P3810 【模板】三维偏序(陌上花开)

当然是\(CDQ\)+树状数组啦

由于没有多少\(Pascal\)的代码,这里贴一个\(Pascal\)的,比较工整美观....

关于三个维的解法

首先第一维,我们可以按照\(point[1,i]\)进行排序(也就是\(a[i]\))。要注意,\(point[1,i]\)为第一关键字,\(point[2,i],point[3,i]\)为第二,第三关键字。这时候要注意,在\(Pascal\)的排序中,不能定义\(mid=(l+r)/2\),而是要定义\(s[1]=point[1,mid],s[2]=point[2,mid],s[3]=point[3,mid]\),不然就会\(GG\)

第二维我们采用\(CDQ\)分治,讲一下做法:

\(I.\)遇到l=r就可以退出了,\(exit\)

\(II.\)先进入\(CDQ(l,mid)\)。一开始是\(CDQ(1,n)\)

\(III.l\)\(r\),\(element[i]\)是这些点的\(point[2,i]\)(也就是第二个点)。然后定义一个\(num[i]:=i\),就是编号。随后进行\(element,num\)为第一,第二关键字排序。

\(IV.l\)\(r\),如果\(i\)的编号小于等于\(mid\)的话,我们就在树状数组中统计,等一会讲第三维的时候会将如何统计。否则就计算共享,等一会也会讲。

\(V.\)还原树状数组,然后进入\(CDQ(mid+1,r)\)

第三维度为什么要用树状数组?我觉得权值线段树比较好理解。我们求逆序对的时候,也就是看它\(1\)~\(i-1\)有多少个数字吧,树状数组就是用来实现这个的。那么插入也就相当于是权值,查询就是查询上述所说。既然树状数组好写,那么就这样子写咯。

\(Code\)

// luogu-judger-enable-o2
var
    n,k,i:longint;
    point:array[1..3,-1..210000] of longint; //坐标点a,b,c
    num,element:array[-1..210000] of longint; //编号和第二坐标,详见下方
    value,ans,tree:array[-1..210000] of longint; //统计答案与树状数组

procedure Sort_1(l,r:longint); //一开始的排序,注意上述所说
var
    i,j,t:longint;
    s:array[1..3] of longint;
begin
    i:=l; j:=r; s[1]:=point[1,(l+r) div 2]; s[2]:=point[2,(l+r) div 2]; s[3]:=point[3,(l+r) div 2];
    repeat
        while (point[1,i]<s[1])or((point[1,i]=s[1])and(point[2,i]<s[2]))or((point[1,i]=s[1])and(point[2,i]=s[2])and(point[3,i]<s[3])) do
        	inc(i);
        while (point[1,j]>s[1])or((point[1,j]=s[1])and(point[2,j]>s[2]))or((point[1,j]=s[1])and(point[2,j]=s[2])and(point[3,j]>s[3])) do
        	dec(j);
        if i<=j then
        begin
        	t:=point[1,i]; point[1,i]:=point[1,j]; point[1,j]:=t;
        	t:=point[2,i]; point[2,i]:=point[2,j]; point[2,j]:=t;
        	t:=point[3,i]; point[3,i]:=point[3,j]; point[3,j]:=t;
            inc(i); dec(j);
        end;
    until i>j;
    if i<r then Sort_1(i,r);
    if j>l then Sort_1(l,j);
end;

procedure Sort_2(l,r:longint); //上述的第二个排序,也就是以第二坐标,编号为第一,第二关键字的排序
var
    i,j,t:longint;
    s:array[1..2] of longint;
begin
    i:=l; j:=r; s[1]:=element[(l+r) div 2]; s[2]:=num[(l+r) div 2];
    repeat
        while (element[i]<s[1])or((element[i]=s[1])and(num[i]<s[2])) do inc(i);
        while (element[j]>s[1])or((element[j]=s[1])and(num[j]>s[2])) do dec(j);
        if i<=j then
        begin
            t:=element[i]; element[i]:=element[j]; element[j]:=t;
            t:=num[i]; num[i]:=num[j]; num[j]:=t;
            inc(i); dec(j);
        end;
    until i>j;
    if i<r then Sort_2(i,r);
    if j>l then Sort_2(l,j);
end;

function lowbit(num:longint):longint; begin exit(num and -num); end; //以下是树状数组常识

procedure Insert(x,add:longint);
begin
    while x<=k do
    begin
        inc(tree[x],add);
        inc(x,lowbit(x));
    end;
end;

function Query(x:longint):longint;
begin
    Query:=0;
    while x>0 do
    begin
        inc(Query,tree[x]);
        dec(x,lowbit(x));
    end;
end;

procedure CDQ(l,r:longint); //cdq分治,l,r为左右两边界
var
    mid,i,j:longint;
begin
    if l=r then exit; //到终点    
    mid:=(l+r) div 2;
    CDQ(l,mid); //先左边

    for i:=l to r do 
    begin
        element[i]:=point[2,i]; //记录第二个坐标
        num[i]:=i; //记录编号
    end;
    Sort_2(l,r); //排序

    for i:=l to r do
        if num[i]<=mid then //编号在左边,算共享
            Insert(point[3,num[i]],1) //直接往这个地方插入,注意是第三坐标的编号
        else
            inc(value[num[i]],Query(point[3,num[i]])); //计算1~第三坐标的共享,注意value里面的是编号
    for i:=l to r do //还原树状数组
        if num[i]<=mid then
            Insert(point[3,num[i]],-1);
    CDQ(mid+1,r); //进入右边
end;

begin
    read(n,k);
    for i:=1 to n do
        read(point[1,i],point[2,i],point[3,i]);
    Sort_1(1,n);
    CDQ(1,n);

    for i:=n-1 downto 1 do //判重复
        if (point[1,i]=point[1,i+1])and(point[2,i]=point[2,i+1])and(point[3,i]=point[3,i+1]) then
            value[i]:=value[i+1];
    for i:=1 to n do //记录答案
        inc(ans[value[i]]);
    for i:=0 to n-1 do
        writeln(ans[i]);
end.

以下是\(8.29\)日对 \(CDQ\) 的补充:

其实都可以用权值线段树或者树状数组来完成此类问题,这里来讲一下 \(CDQ\) 分治。 \(CDQ\) 分治和其它数据结构差不多,几乎都是每多一维,时间复杂度多个 \(log\ n\)。那么二维偏序直接用树状数组,这里不多说。

\(CDQ\) 大体可以认为是 先算出 \(l\)~\(mid\) 的贡献,然后算出 \(l\)\(r\) 的贡献,最后再算 \(mid\)~\(r\) 的贡献。对于 \(l\)~\(mid\)\(mid\)~\(r\) 的贡献,可以直接 \(CDQ(l,mid),CDQ(r,mid)\)。为什么呢?因为分治以后它们会对自己的 \(l\)~\(r\) 算自己的贡献,所以这样子木有问题。现在讨论的重点就是如何求出 \(l\)~\(r\) 的贡献。

三维偏序问题: 偏序问题的第一维,我们是直接排序的。注意要按第 \(1\) 个数组为第 \(1\) 关键字,第 \(2\) 个为第 \(2\) 个关键字 \(.....\)。然后我们就可以保证整个数组 \(a[i]\leq a[j]\ (i\leq j)\)。我们现在有一个区间 \(l,r\) ,我们先 \(CDQ(l,mid)\)。随后我们给 \(l,r\) 这个区间进行编号, \(num[i]:=i\)(这个时候 \(num\) 为编号)。我们再用一个数组 \(element[l\)~\(r]\)\(l\)~\(r\)\(b[i]\),然后进行 \(Sort(l,r)\)。其中 \(element\) 为第一关键字, \(num\) 为第二关键字。

最后循环扫一遍,因为这个时候后已经满足 \(b[i]\leq b[j]\ (i\leq j)\)。我们就可以按照逆序对这样子,对权值线段树(树状数组)插入第三维 \(c[i]\)。如果 \(num[i]\leq mid\) 的话,我们就插入,否则算贡献。为什么这样子呢?因为现在满足的是 \(b[i]\leq b[j]\ (i\leq j)\) ,而 \(num[i]\leq mid\) 可以满足 \(a[l\)~\(mid]\leq a[mid+1\)~\(r]\),我们只需要对 \(c\) 数组进行逆序对一样的操作。

posted @ 2019-01-25 22:09  _ARFA  阅读(132)  评论(0编辑  收藏  举报