2019.2.16

长沙集训 $ Day 6 $

今天没有模拟赛,而是介绍了一种神奇的算法,现在让我们一起了解一下 $ CDQ $ 分治的魅力。


三维偏序问题:

共有 $ n $ 个元素,对于第 $ i $ 个元素,给出三个量:$ a_i \ \ , \ \ b_i \ \ ,\ \ c_i $ ,询问$ a_j<a_i , b_j<b_i , c_j<c_i $ 的元素有多少个。

如果 $ n $ 很小,我们可以用三维前缀和解决这个问题,但当 $ n=10000 $ 或者更大时,显然需要二维树形结构来维护,但树形结构消耗空间大,编程难度高,且处理时常数复杂度高。这时,我们便可以用 $ CDQ $ 分治来解决问题。

先从二位偏序看起:

要解决二维偏序,我们可以先使用快排将第一维处理为有序排列。之后再利用树状数组或分治在第二维上统计答案。(例如逆序对

现在问题变成了三维,我们依然可以沿用解决二维偏序问题的思路。依然用快排处理第一维(例如将其从小到大排列),但这时不能直接将第二维再次排序,否则会打乱第一维的顺序。为解决这个问题,我们可以用分治将数列分成左右两份。注意到左边的 $ a_i $ 一定小于右边的 $ a_j $ ,此时我们将左右两边的 $ b $ 分别排成有序数列,虽然 $ a $ 的顺序会被打乱,但左边 $ a $ 一定是比右边 $ a $ 小。此时,我们便可以统计左边元素对右边元素的贡献。通过层层分治,可以求出所有贡献。

如何排序

对第二维元素可以用快排排序,这样的时间复杂度:

  1. 快排 $ O(n \log n) $
  2. 分治 $ O(\log n) $
  3. 处理答案 $ O(n) $

总时间复杂度接近 $ O(n \log^{2} n) $

这个速度已经可以解决大部分问题,但其实还有更快的处理方式。因为分治的过程类似归并排序,所以我们可以在处理答案时对b进行归并排序,这样的时间复杂度:

  1. 分治 $O(\log n) $
  2. 处理答案+排序 $ O(n) $

总时间复杂度接近 $ O(n \log n) $

处理答案

对答案的处理一般是使用树状数组进行统计,但其实我们可以再次利用 $ CDQ $ 分治的方法,继续对第三维进行分治,即在 $ b $ 排序后,再将区间分为两份,对 $ c $ 进行排序并统计答案。这种 $ CDQ $ 嵌套还可以继续进行,以解决四维、五维偏序问题。

程序( $ CDQ $ 套 $ CDQ $ )

program project1;
type tp=record
  a,b,c,tot,tf,ans:longint;
  tfp:boolean;
end;

var
   f,a,b:array[0..100005]of tp;
   ans:array[0..100005]of longint;
   n,k:longint;

function max(a,b:longint):longint;
begin
  if a>b then exit(a) else exit(b);
end;

procedure re;
var i:longint;
begin
  for i:=1 to n do
    read(f[i].a,f[i].b,f[i].c);
end;

procedure qsort(l,r:longint);
var i,j:longint;
    t,mid:tp;
begin
  i:=l;
  j:=r;
  mid:=f[(i+j) div 2];
  repeat
    while (f[i].a<mid.a) or ((f[i].a=mid.a) and (f[i].b<mid.b)) or ((f[i].a=mid.a) and (f[i].b=mid.b) and (f[i].c<mid.c)) do inc(i);
    while (f[j].a>mid.a) or ((f[j].a=mid.a) and (f[j].b>mid.b)) or ((f[j].a=mid.a) and (f[j].b=mid.b) and (f[j].c>mid.c)) do dec(j);
    if i<=j then begin
      t:=f[i];
      f[i]:=f[j];
      f[j]:=t;
      inc(i);
      dec(j);
    end;
  until i>j;
  if i<r then qsort(i,r);
  if l<j then qsort(l,j);
end;

procedure cdq_c(l,r:longint);
var i,j,mid,t,sum,ii:longint;
begin
  if l=r then begin
    if f[l].tfp=false then begin
      inc(ans[f[l].ans],f[l].tot-1);
      f[l].tfp:=true;
    end;  
    exit;
  end;
  mid:=(l+r) shr 1;
  cdq_c(l,mid);
  cdq_c(mid+1,r);
  i:=l;
  j:=mid+1;
  t:=l-1;
  sum:=0;
  while t<=r-1 do begin
    inc(t);          
    if ((a[i].c<=a[j].c) or (j>r)) and (i<=mid) then begin
      b[t]:=a[i];
      inc(sum,b[t].tf*b[t].tot);
      inc(i);
    end
      else begin
        b[t]:=a[j];        
        inc(j);
      end;
  end;
  for i:=l to r do a[i]:=b[i];
end;

procedure cdq_b(l,r:longint);
var i,mid,j,t:longint;
begin
  if l=r then begin
    cdq_c(l,r);
    exit;
  end;
  mid:=(l+r) shr 1;
  cdq_b(l,mid);
  cdq_b(mid+1,r);
  i:=l;
  j:=mid+1;
  t:=l-1;   
  while t<=r-1 do begin
    inc(t);
    if ((f[i].b<=f[j].b) or (j>r)) and (i<=mid) then begin
      a[t]:=f[i];
      a[t].tf:=1;
      inc(i);
    end
      else begin
        a[t]:=f[j];
        a[t].tf:=0;
        inc(j);
      end;
  end;
  for i:=l to r do f[i]:=a[i];
  cdq_c(l,r);
end;

procedure main;
var i,j:longint;
    sum:array[0..100005]of longint;
begin
  j:=1;
  f[1].tot:=1;
  for i:=2 to n do
    if ((f[i].a=f[j].a) and (f[i].b=f[j].b) and (f[i].c=f[j].c)) then inc(f[j].tot)
      else begin
        inc(j);
        f[j]:=f[i];
        f[j].tot:=1;
      end;
  for i:=1 to j do f[i].ans:=i;
  fillchar(sum,sizeof(sum),0);
  fillchar(ans,sizeof(ans),0);
  cdq_b(1,j);
  for i:=1 to j do inc(sum[ans[f[i].ans]],f[i].tot);
  for i:=0 to n-1 do writeln(sum[i]);
end;

begin
  read(n,k);
  re;
  qsort(1,n);
  main;
end.