CDQ分治学习笔记

CDQ分治学习笔记

1.算法思想

分而治之;然后统计 [ l , m i d ] [l,mid] [l,mid] [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 的贡献。

主要难点:统计左半部分对右半部分的贡献;

主要套路:利用左半部分有序的特点降低常数;把修改等操作转化为偏序关系。

2.使用场景

多维偏序或者一些很难直接处理但是可以转化为偏序关系的题目。

有修改的也可以转化为多了时间维的偏序关系。

3.例题

洛谷 P3810 【模板】三维偏序(陌上花开)

先去重!每个点就是一个非重的三维点对(记得记录出现次数)。

然后预处理先按照第一关键字排序,然后 cdq 分治。

每层在回溯的时候排序第二关键字(可以归并排序)。然后双指针的同时用树状数组记录第三关键字出现的次数。

细节有点多。

Generator(gmoj.net)

题意:【模板】线段树1,但是一开始没有操作,陆陆续续在某些位置上面加上操作,求每次加上操作的答案。可离线。

思路:(比赛时没打暴力,差评,能骗到很多很多部分分的)

考虑最朴素的做法:按照时间一个个加入操作。

如果这是询问操作的话那么答案就要加上只时间在其前面且位置在其前面的所有修改操作对它的贡献区间和统计即可。

如果这是修改操作的话那么答案也要加上它对时间在其后面且位置在其后面的所有查询操作的贡献。这个也需区间和统计。

然后理清思路,那么就变成了操作 i i i,要么 KaTeX parse error: Undefined control sequence: \and at position 42: …]\{j\in{change}\̲a̲n̲d̲ ̲i\in query\} 或者 KaTeX parse error: Undefined control sequence: \and at position 40: …[j]\{j\in query\̲a̲n̲d̲ ̲i\in change\}

然后考虑按照 t i m tim tim 顺序直接 CDQ 分治,就解决掉了 t i m tim tim 这维的偏序问题。

那么我们思考 [ l , m i d ] [l,mid] [l,mid] [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 的答案关系?只需统计左边部分对右边部分的贡献即可。

然后使用归并排序,回溯时候合并两个序列,按照 o r d ord ord 排序(这也是 CDQ 分治的一部分),这就解决了 o r d ord ord 的顺序问题。

合并的时候不一定要用双指针扫描法,按照 o r d ord ord 排序之后判断 t i m ≤ m i d tim\le mid timmid 即可判断原来在左还是在右,而且能够保证 o r d ord ord 顺序。

#include<bits/stdc++.h>
#define getchar() (p1==p2&&(p1=buf,p2=p1+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
char buf[1<<20],*p1=buf,*p2=buf;
using namespace std;typedef int I;typedef long long LL;const I inf=1073741823;I CH,FL;template<typename T>bool in(T&a){for(FL=1;CH!=EOF&&!isdigit(CH);CH=getchar())if(CH=='-')FL=-1;for(a=0;CH!=EOF&&isdigit(CH);CH=getchar())a=a*10+CH-'0';return a*=FL,CH!=EOF;}template<typename T,typename...Args>bool in(T&a,Args&...args){return in(a)+in(args...);}
const I maxn=2e5+50;
struct qry{
	I l,r,val,ord,tim;bool tp;
}Q[maxn],B[maxn];
bool cmp(qry a,qry b){return a.tim<b.tim;}
I blen,opnum[maxn],n,q;bool CR[maxn<<1];LL ans[maxn];
struct BIT{
	LL S[maxn];
	void pls(I x,LL y){for(;x<=n+1;x+=x&-x)S[x]+=y;}
	LL qry(I x){LL ans=0;for(;x;x-=x&-x)ans+=S[x];return ans;}
}C,CI;
void ppls(I l,I r,I val){C.pls(l,val);C.pls(r+1,-val);CI.pls(l,1ll*val*l);CI.pls(r+1,-1ll*(r+1)*val);}
LL qsum(I x){LL res0=C.qry(x),res1=CI.qry(x);return (x+1)*res0-res1;}
LL qqry(I l,I r){return qsum(r)-qsum(l-1);}
void cdq(I l,I r){
	if(l==r)return;
	I mid=l+r>>1,i,j,l1=l-1,r1=r+1,ti,tp;;cdq(l,mid);cdq(mid+1,r);
	for(blen=0,i=mid+1,j=l;i^r1;++i){
		while(j<=mid&&Q[j].ord<Q[i].ord)B[++blen]=Q[j],++j;
		B[++blen]=Q[i];
	}while(j<=mid)B[++blen]=Q[j],++j;
	for(i=l;i^r1;++i)Q[i]=B[i-l+1];
	for(i=l,ti=Q[i].tim,tp=Q[i].tp;i^r1;++i,ti=Q[i].tim,tp=Q[i].tp){
		if(ti<=mid&&tp==0)ppls(Q[i].l,Q[i].r,Q[i].val);
		if(ti>mid&&tp==1)ans[ti]+=qqry(Q[i].l,Q[i].r);
	}for(i=r,ti=Q[i].tim,tp=Q[i].tp;i^l1;--i,ti=Q[i].tim,tp=Q[i].tp)
		if(ti<=mid&&tp==0)ppls(Q[i].l,Q[i].r,-Q[i].val);
	for(i=r,ti=Q[i].tim,tp=Q[i].tp;i^l1;--i,ti=Q[i].tim,tp=Q[i].tp){
		if(ti<=mid&&tp==1)ppls(Q[i].l,Q[i].r,1);
		if(ti>mid&&tp==0)ans[ti]+=qqry(Q[i].l,Q[i].r)*Q[i].val;
	}for(i=r,ti=Q[i].tim,tp=Q[i].tp;i^l1;--i,ti=Q[i].tim,tp=Q[i].tp)
		if(ti<=mid&&tp==1)ppls(Q[i].l,Q[i].r,-1);
}I main(){
	freopen("generator.in","r",stdin);
	freopen("generator.out","w",stdout);
	in(n,q);
	for(I i=1;i<q;++i)in(opnum[i]),Q[opnum[i]].tim=i;
	for(I i=1,op;i<=q;++i){
		in(op,Q[i].l,Q[i].r);Q[i].tp=op-1;Q[i].ord=i;
		if(op==1)in(Q[i].val);
		++Q[i].tim;
	}sort(Q+1,Q+q+1,cmp);
	cdq(1,q);
	for(I i=1;i<=q;++i){
		ans[i]+=ans[i-1];
		printf("%lld\n",ans[i]);
	}return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值