模板:CDQ分治

所谓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 n2×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
*/
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值