CDQ这个东西嘛,说容易其实也很容易,说难其实也有些难,但只要细细品味,定能发现其中的真理的!那真理,也会像蝴蝶一般,破蛹而出,化身为一道亮丽的风景线。 ——题记。
咳咳,闲话就讲到这里了,切入正题。
首先我们来了解一下CDQ分治这个东东。
CDQ分治,他的常数小,但必须离线操作 the most important!
然后,讲讲CDQ的答题思路。
first,分。将[l,r]分成[l,mid]和[mid+1,r](mid=l+r>>1)
then,入。进入两区间,直至l==r才return
after that,求。求[l,mid]区间对[mid+1,r]的贡献。
OK,下面来讲讲例题,这样才能懂吧。。。
1:裸的CDQ——逆序对统计(一维偏序)
给出n,和长为n的序列,让你求着逆序对个数。
sample input:
3
1 2 3
sample output:
0
100%:n<=5*10^5
这题很简单吧,树状数组或是归并排序(就是CDQ分治的基本使用)。
先来个树状数组的:
#include<cstdio>
#include<algorithm>
#define ll long long
#define lowbit(x) x&-x
#define N 500010
using namespace std;
struct node{ll val,num;}a[N];
ll ans=0,t[N]={0},c[N];int n,tot=1;
inline int read()
{
int x=0,f=1; char c=getchar();
while(c<'0' || c>'9') f=(c=='-') ? -1:f,c=getchar();
while(c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
int cmp(node x,node y) {return x.val<y.val;}
void add(int x) {for (;x<=n;x+=lowbit(x)) t[x]++;}
ll total(int x) {ll s=0; for (;x>0;x-=lowbit(x)) s+=t[x]; return s;}
int main()
{
n=read();
for (int i=1;i<=n;i++) a[i]=(node){read(),i};
sort(a+1,a+n+1,cmp);c[a[1].num]=1;
for (int i=2;i<=n;i++)
{
if (a[i].val!=a[i-1].val) tot++;
c[a[i].num]=tot;
}
for (int i=n;i>0;i--)
add(c[i]),ans+=total(c[i]-1);
printf("%lld\n",ans);
return 0;
}
再来一段CDQ分治(也就是归并)的。
#include<cstdio>
using namespace std;
int n,a[500010],b[500010];
long long ans=0;
inline int read()
{
int x=0; char c=getchar();
while (c<'0' || c>'9') c=getchar();
while (c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x;
}
void CDQ(int l,int r)
{
if (l==r) return;
int mid=l+r>>1;
CDQ(l,mid),CDQ(mid+1,r);
int le=l,ri=mid+1,now=l;
while (le<=mid && ri<=r)
{
if (a[le]<=a[ri] && le<=mid) b[now++]=a[le++];
else ans+=mid-le+1,b[now++]=a[ri++];
}
while (le<=mid) b[now++]=a[le++];
while (ri<=r) b[now++]=a[ri++];
for (int i=l;i<=r;i++) a[i]=b[i];
}
int main()
{
n=read();
for (int i=1;i<=n;i++) a[i]=read();
CDQ(1,n);
printf("%lld\n",ans);
return 0;
}
2:裸的CDQ——二维偏序
给出n,以及n对(x,y)
求对于每一对(x,y)共有多少(x1,y1)满足x>=x1&y>=y1
n<=10^5
真的,这道题其实也很简单的。
一维排序,另一维CDQ分治即可。
#include<cstdio>
#include<algorithm>
using namespace std;
struct node{int x,y,fr;}a[100010],b[100010];
int n,c[100010];
inline int read()
{
int x=0; char c=getchar();
while (c<'0' || c>'9') c=getchar();
while (c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x;
}
int cmp(node x,node y) {return x.x<y.x;}
void CDQ(int l,int r)
{
if (l==r) return;
int mid=l+r>>1;
CDQ(l,mid),CDQ(mid+1,r);
int le=l,ri=mid+1,now=l;
while (le<=mid && ri<=r)
{
if (a[le].y<a[ri].y) b[now++]=a[le++];
if (a[le].y>a[ri].y) c[a[ri].fr]+=mid-le+1,b[now++]=a[ri++];
else b[now++]=a[le++];
}
while (le<=mid) b[now++]=a[le++];
while (ri<=r) b[now++]=a[ri++];
for (int i=l;i<=r;i++) a[i]=b[i];
}
int main()
{
n=read();
for (int i=1;i<=n;i++)
a[i].x=read(),a[i].y=read(),a[i].fr=i;
sort(a+1,a+n+1,cmp);
CDQ(1,n);
for (int i=1;i<=n;i++)
printf("%d ",c[i]);
return 0;
}
3:裸的CDQ——陌上花开(三维偏序)
本题洛谷上有,可自己做一下。
对于这题,我们无语。。。。。。
自己手贱,打了个CDQ套CDQ。。。
一维排序,另外两维就两个CDQ即可。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 100010
using namespace std;
int n,k,ans[maxn],d[maxn];
struct node{int x,y,z,*ans;bool b;
bool operator==(const node &a)
const {return x==a.x&&y==a.y&&z==a.z;}
}a[maxn],b[maxn],c[maxn];
inline bool cmp(const node &a,const node &b)
{
return a.x==b.x ? (a.y==b.y ? a.z<b.z:a.y<b.y):a.x<b.x;
}
inline int read()
{
int x=0; char c=getchar();
while (c<'0' || c>'9') c=getchar();
while (c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x;
}
void CDQ2(int l,int r)
{
if (l==r) return;
int mid=l+r>>1;
CDQ2(l,mid),CDQ2(mid+1,r);
for (int i=l,j=l,k=mid+1,cnt=0;i<=r;i++)
if ((k>r || b[j].z<=b[k].z) && j<=mid) c[i]=b[j++],cnt+=c[i].b;
else c[i]=b[k++],*c[i].ans+=cnt*(!c[i].b);
for (int i=l;i<=r;i++) b[i]=c[i];
}
void CDQ(int l,int r)
{
if (l==r) return;
int mid=l+r>>1;
CDQ(l,mid),CDQ(mid+1,r);
for (int i=l,j=l,k=mid+1;i<=r;i++)
if ((k>r || a[j].y<=a[k].y) && j<=mid) b[i]=a[j++],b[i].b=1;
else b[i]=a[k++],b[i].b=0;
for (int i=l;i<=r;i++) a[i]=b[i];
CDQ2(l,r);
}
int main()
{
n=read(),k=read();
for (int i=1;i<=n;i++)
a[i].x=read(),a[i].y=read(),a[i].z=read(),a[i].ans=&ans[i],ans[i]=0;
sort(a+1,a+n+1,cmp);
for (int i=n-1;i>0;i--)
if (a[i]==a[i+1]) *a[i].ans=*a[i+1].ans+1;
CDQ(1,n);
for (int i=1;i<=n;i++) ++d[ans[i]];
for (int i=0;i<n;i++) printf("%d\n",d[i]);
return 0;
}
总的来说,CDQ分治是可以学的!!!