所谓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 × 1 0 5 n\le 2\times10^5 n≤2×105
CDQ分治的经典套路:先递归求左右内部贡献,再求左右对互相的贡献
先按照x排序,然后每次合并的时候两边分别按照y排序,利用双指针维护树状数组累加答案即可
时间复杂度
O
(
n
log
2
n
)
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);
#endif
n=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
(
n
log
2
n
)
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);
#endif
n=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
*/