偏序问题及CDQ分治详解

CDQ用来解决分治时左半部分对右半部分造成影响的问题。

CDQ分治的经典问题是三维偏序问题。

要想解决三维偏序问题,首先你要知道什么是偏序。(废话

一维偏序:

给出直线上的n个点,问有多少对点满足xi<=xj

对于这个问题,直接排序就可以了。

二维偏序:

给定平面内的n个点,问有多少对点满足xi<=xj且yi<=yj

这是个经典的树状数组问题,相信学过树状数组的人一定都做过·一道叫做数星星的题,这道题就是经典的二维偏序问题,并不需要二维数组,我们可以通过按x坐标为第一关键字排序,从而消除x坐标给答案带来的影响。然后我们用一个树状数组维护前缀和,记录之前有多少点的y坐标比该点小,由于在之前的x坐标一定比较小,因此只要保证y坐标即可。

三维偏序:

给出空间内的n个点,问有多少点对满足xi<=xj且yi<=yj且zi<=zj

树套树???码量++,空间++

在不强制在线的情况下,我们完全可以使用CDQ分治这种东西来简化一层树结构,因此三维偏序问题实际上可以这样处理:

1)仿照二位偏序问题,先给x排序,消除其影响。

2)使用CDQ分治,消除y的影响

3)使用数组维护z的前缀,统计答案。

1)、3)都是二维偏序的正常步骤,下面重点来讲讲CDQ分治

前边已经讲过了,CDQ分治要处理左区间对右区间的贡献问题。

其实很简单,我们把每一步操作分成三步:

1)递归处理左区间;2)递归处理右区间;3)处理左区间对右区间的贡献。

在三维偏序问题中,之所以不考虑右区间对左区间的贡献,是因为按x排序了,因此右区间一定不会对左区间造成贡献。

然后我们直接就按y排序,然后看一个东西是在左区间还是右区间,是左区间就放进一个树状数组中,否则直接求贡献。

因为CDQ直接分治到最小的单位,也就是一个点,因此可以保证所有答案都是没有遗漏的。

为了保证答案的不重复性,我们可以在求完贡献后把树状数组内贡献再撤消;

例题:洛谷P3810:陌上花开

参考代码如下:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<queue>
 5 #define N 500005
 6 #define lowbit(x) x&-x
 7 using namespace std;
 8 int read()
 9 {
10     int x=0,f=1;char ch=getchar();
11     while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
12     while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
13     return x*f;
14 }
15 struct node{int x,y,z,id;}a[N];
16 bool cmpa(node a,node b)
17 {
18     if(a.x!=b.x)return a.x<b.x;
19     if(a.y!=b.y)return a.y<b.y;
20     return a.z<b.z;
21 }
22 bool cmpb(node a,node b)
23 {
24     if(a.y!=b.y)return a.y<b.y;
25     if(a.x!=b.x)return a.x<b.x;
26     return a.z<b.z;
27 }
28 int n,k,tot,c[500005],b[500005],f[500005],s,j,block[500005];
29 void modify(int x,int m){for(;x<=k;x+=lowbit(x))c[x]+=m;}
30 int query(int x){int res=0;for(;x;x-=lowbit(x))res+=c[x];return res;}
31 void cdq(int l,int r)
32 {
33     if(l==r)return;
34     int mid=(l+r)/2;
35     cdq(l,mid);cdq(mid+1,r);
36     sort(a+l,a+r+1,cmpb);
37     for(int i=l;i<=r;i++)(a[i].x<=mid)?modify(a[i].z,1),s=1:b[a[i].id]+=query(a[i].z);
38     for(int i=l;i<=r;i++)if(a[i].x<=mid)modify(a[i].z,-1);
39 }
40 int main()
41 {    
42     ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
43     cin>>n>>k;//n=read();k=read();
44     for(int i=1;i<=n;i++)
45     {
46         cin>>a[i].x>>a[i].y>>a[i].z;//=read();a[i].y=read();a[i].z=read();
47         a[i].id=i;
48     }
49     sort(a+1,a+1+n,cmpa);
50     for(int i=1;i<=n;)
51     {
52         j=i+1;
53         while(j<=n&&a[i].x==a[j].x&&a[i].y==a[j].y&&a[i].z==a[j].z)j++;
54         while(i<j)block[a[i].id]=a[j-1].id,i++;
55     }
56     for(int i=1;i<=n;i++)a[i].x=i;
57     cdq(1,n);
58     for(int i=1;i<=n;i++)f[b[block[a[i].id]]]++; 
59     for(int i=0;i<n;i++)printf("%d\n",f[i]);
60     return 0;
61 }
三位偏序模板代码

 

转载于:https://www.cnblogs.com/szmssf/p/11481094.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值