前言:
之前一直想学来着。。。一直都忘了
这次vj上老师放了几道分治题,刚好有CDQ
开始我啥也不会,听说是主席树,就去学了学
危险行为,请勿模仿!
然后。。。就溜回来了
从三位偏序敲起到动态逆序对,杠了我两天。。。
还是没学会?(不存在的)
现在写篇博客整理一下
正文:
一.CDQ分治是怎样的算法?
CDQ分治算法是基于时间的分治算法 ——李煜东《算法竞赛进阶指南》
光看这句话,相信你会和我一样一脸懵逼
没事还有下句
剩下的我懒得打了,相信作为一个OIer你一定有lyd的这本书吧(要第二版哦!),翻到238页,再食用本博客最佳QAQ
我的理解如下:
对于一个修改"A"(以下简称A)以及询问"B"(以下简称B),
它们在时间序列中的位置分别为a,b(这里我们令$a lt b$)
假设我们当前处理的区间为[l,r],mid为区间中点(即 $mid=(l+r)/2$ )
那么a,b的位置无非只有三种:
1.a在序列左侧,b在序列右侧((a<mid,b>mid))
2.a,b都在序列左侧$(a<mid,b<mid)$
3.a,b都在序列右侧((a>mid,b>mid))
那我们来在各个情况下考虑A对B的影响:
1.此时我们会直接跨过mid,计算"影响"
2.会一直递归左区间,那么一定会递归到一次,能使A,B满足1(也就是a在序列左侧,b在序列右侧)
3.同2
因此,CDQ分治能做到不重不漏(因为每一对修改与询问都只会在有且仅有一次的递归中跨过中点)
(看到这里你可能还是不知道这意味着什么,
但当你结合下列例题后思考,收获会更大)
二.例题
这一题是非常经典的CDQ板子题
题目就是要保证(a_j leq a_i,b_j leq b_i,c_j leq c_i)
思路的话还是比较直接,
第一维排序,第二维CDQ,第三维树状数组
代码:
#include<bits/stdc++.h> #define R register int #define gc getchar using namespace std; int _n,n,k,c[200005]; int cnt[200005]; int rd() { int ans=0,flag=1; char ch=gc(); while((ch>'9'||ch<'0')&&ch!='-')ch=gc(); if(ch=='-')flag=-1,ch=gc(); while(ch>='0'&&ch<='9')ans=ans*10+ch-48,ch=gc(); return ans*flag; } struct node{ int ans,x,y,z,w; }a[100005],b[100005]; bool cmpx(node a,node b) { return a.x<b.x||(a.x==b.x&&a.y<b.y)||(a.x==b.x&&a.y==b.y&&a.z<b.z); } bool cmpy(node a,node b) { return a.y<b.y||(a.y==b.y&&a.z<b.z); } bool equal(node a,node b) { return a.x==b.x&&a.y==b.y&&a.z==b.z; } int ask(int x) { int ans=0; for(;x;x-=x&-x)ans+=c[x]; return ans; } void add(int x,int d) { for(;x<=k;x+=x&-x)c[x]+=d; } void CDQ(int l,int r) { if(l==r)return; int mid=(l+r)>>1; CDQ(l,mid); CDQ(mid+1,r); sort(a+l,a+mid+1,cmpy); sort(a+mid+1,a+r+1,cmpy); int i=mid+1,j=l; while(i<=r) { while(a[j].y<=a[i].y&&j<=mid) { add(a[j].z,a[j].w); j++; } a[i].ans+=ask(a[i].z); i++; } for(i=l;i<j;i++)add(a[i].z,-a[i].w); } int main() { _n=rd(),k=rd(); for(R i=1;i<=_n;i++) { b[i].x=rd(),b[i].y=rd(),b[i].z=rd(); } sort(b+1,b+_n+1,cmpx); for(R i=1;i<=_n;i++) { int mul=1; while(equal(b[i],b[i+1])&&i<_n) { i++; mul++; } a[++n]=b[i]; a[n].w=mul; } CDQ(1,n); for(R i=1;i<=n;i++) { cnt[a[i].ans+a[i].w-1]+=a[i].w; } for(R i=0;i<_n;i++) { cout<<cnt[i]<<endl; } return 0; }
题目思路也很简单,
逆序对怎么求你总知道吧。。。
就先求个逆序对
再对每个删去的元素删去他的贡献(这个你都没了,那么与它构成的逆序对也不复存在)
但是你会发现会删去某些"不需要删去的点"
出现这种情况的原因是一个点在删贡献时必然会删掉某些比他还先删的点
那么这些点就咕咕咕了
所以你不能直接去减.
考虑对于每一个i,
他能影响到的点j必然满足:
(pos_i>pos_j,val_i<val_j,deletetime_i>deletetime_j或者pos_i<pos_j,val_i>val_j,deletetime_i>deletetime_j)
于是就可以用三维偏序的思路来做这一部分
至于什么树状数组要清空,数组要开longlong,第一个括号要换行这些细节我就不说了
还有代码的一些注释我会陆续加上,目前还比较丑,请见谅!
代码:
#include<bits/stdc++.h> #define R register int #define gc getchar #define ll long long using namespace std; ll n,m,c[100005],Map[100005],Del[100005],ans; ll rd() { ll ans=0,flag=1; char ch=gc(); while((ch<'0'||ch>'9')&&ch!='-')ch=gc(); if(ch=='-')flag=-1,ch=gc(); while(ch>='0'&&ch<='9')ans=ans*10+ch-48,ch=gc(); return ans*flag; } struct node{ int t,pos,val; ll ans1,ans2; }a[100005]; ll b[100005]; bool cmp_pos(node a,node b){return a.pos>b.pos;} bool cmp_pos2(node a,node b){return a.pos<b.pos;} bool cmp(node aa,node bb){return aa.pos<bb.pos;} bool cmp_time1(node a,node b){return a.t>b.t;} bool cmp_time2(node a,node b){return a.t<b.t;} bool cmp_val1(node a,node b){return a.val>b.val;} bool cmp_val2(node a,node b){return a.val<b.val;} void add(ll x,ll d){for(;x<=n;x+=x&-x)c[x]+=d;} ll ask(ll x){ll ans=0;for(;x;x-=x&-x)ans+=c[x];return ans;} void CDQ1(ll l,ll r) { if(l==r)return; ll mid=(l+r)>>1; CDQ1(l,mid);CDQ1(mid+1,r); //sort(a+l,a+mid+1,cmp_val1); //sort(a+mid,a+r+1,cmp_val1); ll i=l,j=mid+1; while(j<=r) { while(i<=mid&&a[i].val>a[j].val){add(a[i].pos,1),i++;} a[j].ans1+=ask(a[j].pos); j++; } for(ll j=l;j<i;j++)add(a[j].pos,-1); sort(a+l,a+r+1,cmp_val1); } void CDQ2(ll l,ll r) { if(l==r)return; ll mid=(l+r)>>1; CDQ2(l,mid);CDQ2(mid+1,r); //sort(a+l,a+mid+1,cmp_pos); //sort(a+mid,a+r+1,cmp_pos); ll i=l,j=mid+1; while(j<=r) { while(i<=mid&&a[i].pos>a[j].pos){add(a[i].val,1),i++;} a[j].ans2+=ask(a[j].val); j++; } for(ll j=l;j<i;j++)add(a[j].val,-1); sort(a+l,a+r+1,cmp_pos); } int main() { n=rd(); m=rd(); for(R i=1;i<=n;i++) { b[i]=rd(); a[b[i]].pos=i; a[b[i]].val=b[i]; //Map[a[i].x]=i; ans+=i-1-ask(b[i]); add(b[i],1); //a[i].ans+=ask() } memset(c,0,sizeof(c)); for(R i=1;i<=m;i++) { ll t=rd(); a[t].t=i; } sort(a+1,a+n+1,cmp_pos2); for (int i=1,j=m+1;i<=n;i++) { if (a[i].t==0) a[i].t=j++; //x这一维是删除的时间,这里赋值成inf也对 } sort(a+1,a+n+1,cmp_time1); CDQ1(1,n); sort(a+1,a+n+1,cmp_time1); CDQ2(1,n); sort(a+1,a+n+1,cmp_time2); for(R i=1;i<=m;i++) { cout<<ans<<endl; //cout<<a[i].ans1<<' '<<a[i].ans2<<endl; ans=ans-(ll)a[i].ans1-(ll)a[i].ans2; } return 0; }
应该会upd吧,我也不好说