文章目录
- 前言
- upd
- 例题
- P3810 【模板】三维偏序(陌上花开)
- P2487 [SDOI2011]拦截导弹
所谓CDQ分治,就是和由Conprour、Doctorjellyfish、QE添一同发明的分治算法
(逃)
前言
神奇的乱搞黑科技
CDQ分治能够通过更小的时间常数和更简单的代码难度完爆一些大算法,如树套树、splay凸包等。
应用条件:询问离线,修改独立
upd
from KHIN:
归并排序的时候可以直接调用 c++98
中的 inplace_merge(l,mid,r,cmp)
。
表示把 [l,mid)
和 [mid,r)
的有序数组按照 cmp
排序。
例题
P3810 【模板】三维偏序(陌上花开)
有n个元素,每个元素有三个特征值,元素a大于元素b当且仅当a的三个特征值都大于等于b
设 f(i) 表示a大于的元素数量(不含自己),对于每一个 i ,求出 f(x)=i 的 x 的数目
n≤2×105n\le 2\times10^5n≤2×105
CDQ分治的经典套路:先递归求左右内部贡献,再求左右对互相的贡献
先按照x排序,然后每次合并的时候两边分别按照y排序,利用双指针维护树状数组累加答案即可
时间复杂度O(nlog2n)O(n\log^2n)O(nlog2n)
细节上,注意完全相同的元素需要特殊处理,树状数组不要忘记清空
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=2e5+100;
const int mod=998244353;
inline ll read() {ll x(0),f(1);char c=getchar();while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}return x*f;
}int n,m,k;
struct node{int a,b,c,id,w;
}p[N],P[N];
bool cmpa(node u,node v){if(u.a!=v.a) return u.a<v.a;else if(u.b!=v.b) return u.b<v.b;return u.c<v.c;
}
bool cmpb(node u,node v){if(u.b!=v.b) return u.b<v.b;else return u.c<v.c;
}int f[N];
inline void add(int p,int w){for(;p<=m;p+=p&-p) f[p]+=w;return;
}
inline int ask(int p){int res(0);for(;p;p-=p&-p) res+=f[p];return res;
}int ans[N],bac[N];
void solve(int l,int r){if(l==r) return;int mid=(l+r)>>1;solve(l,mid);solve(mid+1,r);//printf("\nsolve:(%d %d)\n",l,r);sort(p+l,p+mid+1,cmpb);sort(p+mid+1,p+r+1,cmpb);int pl=l;for(int i=mid+1;i<=r;i++){while(pl<=mid&&p[pl].b<=p[i].b){add(p[pl].c,p[pl].w);//printf(" add: (%d %d %d) w=%d\n",p[pl].a,p[pl].b,p[pl].c,p[pl].w);++pl;}ans[p[i].id]+=ask(p[i].c);//printf(" query: (%d %d %d) add=%d\n",p[i].a,p[i].b,p[i].c,ask(p[i].c));}for(int i=l;i<pl;i++) add(p[i].c,-p[i].w);return;
}
int main(){
#ifndef ONLINE_JUDGE//freopen("a.in","r",stdin);//freopen("a.out","w",stdout);
#endifn=read();m=read();for(int i=1;i<=n;i++){P[i]=(node){(int)read(),(int)read(),(int)read()};}sort(P+1,P+1+n,cmpa);int tot(0);for(int i=1;i<=n;i++){if(i>1&&P[i-1].a==P[i].a&&P[i-1].b==P[i].b&&P[i-1].c==P[i].c) p[tot].w++;else{p[++tot]=P[i];p[tot].w=1;p[tot].id=tot;}}swap(tot,n);solve(1,n);for(int i=1;i<=n;i++){bac[ans[p[i].id]+p[i].w-1]+=p[i].w;//printf("(%d %d %d) ans=%d\n",p[i].a,p[i].b,p[i].c,ans[p[i].id]);}for(int i=0;i<tot;i++) printf("%d\n",bac[i]);return 0;
}
/*
*/
P2487 [SDOI2011]拦截导弹
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度、并且能够拦截任意速度的导弹,但是以后每一发炮弹都不能高于前一发的高度,其拦截的导弹的飞行速度也不能大于前一发。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
在不能拦截所有的导弹的情况下,我们当然要选择使国家损失最小、也就是拦截导弹的数量最多的方案。但是拦截导弹数量的最多的方案有可能有多个,如果有多个最优方案,那么我们会随机选取一个作为最终的拦截导弹行动蓝图。
我方间谍已经获取了所有敌军导弹的高度和速度,你的任务是计算出在执行上述决策时,每枚导弹被拦截掉的概率。
利用CDQ分治优化1D-1D的DP
先递归处理出左半区间的dp值,然后尝试用左半区间的dp值更新右半区间,再递归处理右半区间
利用树状数组维护前/后缀最大值保证复杂度
时间复杂度O(nlog2n)O(n\log^2n)O(nlog2n)
注意:在递归处理右半区间之前,需要先按照下标重新sort一下使右半区间重新变得有序
#include<bits/stdc++.h>
using namespace std;
#define ll __int128
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=2e5+100;
const int mod=1e9;
inline ll read() {ll x(0),f(1);char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}return x*f;
}int n,m,k;
int q[N],cnt;
struct node{int a,b,id;bool operator < (const node o){return a>o.a;}
}p[N];
bool cmp(node u,node v){return u.id<v.id;
}
struct Dp{int val;ll num;
}dp1[N],dp2[N];
void operator += (Dp &a,Dp b){if(b.val>a.val) a=b;else if(b.val==a.val) a.num+=b.num;return;
}Dp f[N];
inline void Upd(int p,Dp w){for(;p<=cnt;p+=p&-p) f[p]+=w;return;
}
inline Dp Ask(int p){Dp res=(Dp){0,0};for(;p;p-=p&-p) res+=f[p];return res;
}
inline void Clear(int p){for(;p<=cnt;p+=p&-p) f[p]=(Dp){0,0};return;
}
void solve2(int l,int r){if(l==r){++dp2[l].val;return;}int mid=(l+r)>>1;solve2(mid+1,r);//printf("\nsolve: (%d %d)\n",l,r);sort(p+l,p+mid+1);sort(p+mid+1,p+r+1);int pl=r;for(int i=mid;i>=l;i--){while(pl>mid&&p[pl].a<=p[i].a){Upd(p[pl].b,dp2[p[pl].id]);//printf(" add: i=%d DP:(%d %d)\n",p[pl].id,dp2[pl].val,(int)dp2[pl].num);--pl;}dp2[p[i].id]+=Ask(p[i].b);//printf(" update: i=%d DP:(%d %d)\n",p[i].id,dp2[i].val,(int)dp2[i].num);}for(int i=r;i>pl;i--) Clear(p[i].b);sort(p+l,p+mid+1,cmp);solve2(l,mid);return;
}inline void upd(int p,Dp w){p=cnt-p+1;for(;p<=cnt;p+=p&-p) f[p]+=w;return;
}
inline Dp ask(int p){p=cnt-p+1;Dp res=(Dp){0,0};for(;p;p-=p&-p) res+=f[p];return res;
}
inline void clear(int p){p=cnt-p+1;for(;p<=cnt;p+=p&-p) f[p]=(Dp){0,0};return;
}
void solve1(int l,int r){if(l==r){++dp1[l].val;return;}int mid=(l+r)>>1;solve1(l,mid);sort(p+l,p+mid+1);sort(p+mid+1,p+r+1);//printf("\nsolve: (%d %d)\n",l,r);int pl=l;for(int i=mid+1;i<=r;i++){while(pl<=mid&&p[pl].a>=p[i].a){upd(p[pl].b,dp1[p[pl].id]);++pl;//printf(" add: i=%d DP:(%d %d)\n",p[i].id,dp1[i].val,(int)dp1[i].num);}dp1[p[i].id]+=ask(p[i].b);//printf(" update: i=%d DP:(%d %d)\n",p[i].id,dp1[i].val,(int)dp1[i].num);}for(int i=l;i<pl;i++) clear(p[i].b);sort(p+mid+1,p+r+1,cmp);solve1(mid+1,r);return;
}int main(){
#ifndef ONLINE_JUDGE//freopen("a.in","r",stdin);//freopen("a.out","w",stdout);
#endifn=read();for(int i=1;i<=n;i++){p[i].a=read();p[i].b=read();p[i].id=i;dp1[i].num=dp2[i].num=1;q[++cnt]=p[i].a;q[++cnt]=p[i].b;}sort(q+1,q+1+cnt);cnt=unique(q+1,q+1+cnt)-q-1;for(int i=1;i<=n;i++){p[i].a=lower_bound(q+1,q+1+cnt,p[i].a)-q;p[i].b=lower_bound(q+1,q+1+cnt,p[i].b)-q;}solve1(1,n);sort(p+1,p+1+n,cmp);solve2(1,n);ll sum=0;int ans(0);for(int i=1;i<=n;i++){if(dp1[i].val>ans){ans=dp1[i].val;sum=dp1[i].num;}else if(dp1[i].val==ans){sum+=dp1[i].num;}}printf("%d\n",ans);for(int i=1;i<=n;i++){//printf("i=%d dp1=(%d %d) dp2=(%d %d)\n",i,dp1[i].val,(int)dp1[i].num,dp2[i].val,(int)dp2[i].num);if(dp1[i].val+dp2[i].val-1==ans){printf("%.5lf ",1.0*dp1[i].num*dp2[i].num/sum);}else printf("0.00000 ");}return 0;
}
/*
10
23 7
63 14
84 57
40 74
96 79
20 27
48 37
86 70
66 28
86 47
*/