今日初学cdq分治,特来写个小小的总结。
首先看这个名字就知道和分治有关,前缀名其实没那么重要。一上午学习下来个人感觉就是这玩意不就是在归并上加了个外挂吗……
直接看最模板的题目,陌上花开。
这名字确实很诗意,难得。本想着题面里可能也会有类似的表述,然鹅并没有。终究是错付了。
题意很简单,求三维偏序。
按照cdq的方法,首先直接对第一维进行排序,这就说明了只有前面的点可能更新到后面的点,这就可以搞分治了;然后发现,既然是分治用左边更新右边,那么元素们就只有左右之分,也就是说左区间和右区间两个子区间内的元素内部顺序便不再重要(详情请看《乌合之众》和《*分子》),便可以考虑对它们的第二维进行动态的排序(这和归并是一模一样的),再最后以第三维的权值为桶用树状数组查询答案即可。复杂度\(O(NlogN)\)。
这就是cdq的基本思想,基于部分排序达到减少维度的效果。当然这其中有一些需要注意的地方:
- 最后树状数组清空为下一次做准备时尽量不用传统的memset,容易超时(或者说肯定会超时,因为memset会让复杂度达到 \(O(N^2)\) 左右),特别惨,还不如暴力。
- 关于顺序需要具体情况具体分析。这里的顺序是指先处理右区间还是先合并。这道题是先处理右区间,但对于某些问题就必须先合并,比如不那么经典的LIS计数问题就应当先合并再处理右区间。具体原因仍在思考,以后再总结罢。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define zczc
using namespace std;
const int N=100010;
const int M=200010;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
wh*=f;return;
}
int m,n,cnt,ans[N];
struct no{
int x,y,z,num,ans;
}a[N],b[N];//b为归并排序的辅助数组
bool operator ==(no s1,no s2){
return s1.x==s2.x&&s1.y==s2.y&&s1.z==s2.z;
}
inline bool cmp1(no s1,no s2){
if(s1.x!=s2.x)return s1.x<s2.x;
else if(s1.y!=s2.y)return s1.y<s2.y;
else return s1.z<s2.z;
}
#define lowbit (wh&-wh)
int t[M];
inline void clear(){
memset(t,0,sizeof(t));
return;
}
inline void init(int wh){
for(;wh<=n;wh+=lowbit)t[wh]=0;
}
inline void change(int wh,int val){
for(;wh<=n;wh+=lowbit)t[wh]+=val;
return;
}
inline int work(int wh){
int an=0;
for(;wh;wh-=lowbit)an+=t[wh];
return an;
}
#undef lowbit
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=mid+1,k=l;k<=r;k++){
if(j>r||(i<=mid&&a[i].y<=a[j].y)){
b[k]=a[i++];
change(b[k].z,b[k].num);
}
else{
b[k]=a[j++];
b[k].ans+=work(b[k].z);
}
}
for(int i=l;i<=r;i++){
a[i]=b[i];
init(b[i].z);
}
return;
}
signed main(){
#ifdef zczc
freopen("in.txt","r",stdin);
#endif
read(m);read(n);
for(int i=1;i<=m;i++){
read(a[i].x);read(a[i].y);read(a[i].z);a[i].num=1;
}
sort(a+1,a+m+1,cmp1);
for(int i=1;i<=m;i++){
if(cnt!=0&&a[cnt]==a[i])a[cnt].num++;
else a[++cnt]=a[i];
}
cdq(1,cnt);
for(int i=1;i<=cnt;i++)ans[a[i].ans+a[i].num-1]+=a[i].num;
for(int i=0;i<m;i++)printf("%d\n",ans[i]);
return 0;
}
双倍经验: 动态逆序对,稍微转化一下就好了。懒得写了。