cdq分治

虽然题还没写完,但是还是写一下吧。

cdq分治

orz cdq
cdq分治离线处理balabala
一维排序,二维cdq,三维数据结构

二维偏序

首先对于用归并排求逆序对都知道啦。
不过这里为了方便,求正序对好了。

n个数,求这n个数中对于第i个数 j &lt; i j&lt;i j<i a j &lt; a i a_j&lt;a_i aj<ai的数有多少

这对应的是一个二维偏序问题(一维位置,一维数值)。
由于位置在给出的时候已经有序了,所以也可以说处理的其实只有一维。(好废哦)
应为条件有两个,所以我们会希望,在处理比较数值的时候,其位置已经满足要求。
回顾一下归并排序
当解决子问题solve(l,r)时,我们先会解决solve(l,mid),solve(mid+1,r);使这两部分都有序。
在用cdq分治解决问题solve(l,r)是,也是先解决solve(l,mid),solve(mid+1,r),并且使这两部分各自按照数值排序。
由于这两个子问题到目前为止是相对独立的,所以l~mid区间的数的位置一定在mid+1 ~r区间的位置之前,所以让(l,mid)区间的数去为(mid+1,r)区间的数增加答案.
假设有5个数则算法流程就像是这样(箭头表示的是一种更新的关系)
在这里插入图片描述
其实很显然每个数都会且仅会被它前面的数更新一遍
对于第x个数来讲它的漫漫合并路类似于这样(x,x)–(x-1,x)–(x-3,x)…
然后它就被更新。。。
部分代码参考如下:

   int sum=0;
	while (ll<=mid&&rr<=r) {
		if (a[ll].v<=a[rr].v) {
            g[++k]=a[ll++];
			sum++;
	    }
		else {
		//(l,mid)区间内比它小且在它前面的数都统计过了,这意味着在这次合并中它完成了使命
            g[++k]=a[rr];
			ans[a[rr].id]+=sum;
			rr++;
	    }
    }
    while (ll<=mid) g[++k]=a[ll++];//这个时候它的贡献已经没人要了,不统计也罢
    while (rr<=r) {
		ans[a[rr].id]+=sum;
		g[++k]=a[rr++];
    }

v是数值,id表示它是第几个数(编号,为了输出答案),g数组是临时存放用的。
sum作为临时变量,统计贡献。
到此为止,它其实就像一个归并排序:(

树状数组模板(hh)

现在有若干数,并且有两种操作
1.修改,将第x个数加k
2.询问,第l~r个数的和,请输出

可以尝试用cdq分治搞
首先将询问(就是求区间和)转成 s u m l − s u m r sum_l-sum_r sumlsumr(sum是前缀和)。
对于初始值,也可以将其看做在某一个位置上加上某数(它的操作时间自然最先).
所以两种操作,修改,求前缀和。
这同样是一个二维偏序问题,第一维是操作时间,第二维是操作位置。对于修改来讲其实这些并不重要。但对于询问,只有操作时间在它之前,且位置在它之前才有影响对吧,这和上一个问题不就很相似吗。在这个问题中操作时间给出时就有序(不然就排个序),所以只需用cdq搞第二维就好。
代码参考:
实际上在这份代码里,对于询问拆成了两个操作。
sum(l,r)=sum(1,r)-sum(1,l).
sum(1,l)是要减的
用a数组来记录操作,三个变量type,nu,val
type表示类型
1:修改。nu表示修改位置,val表示修改值。
2:加前缀和。nu表示位置(右端点),val表示这是第几个询问(用于输出)
3:减前缀和。其余与2同

#include <bits/stdc++.h>
using namespace std;
int n,m;
long long ans[400010];
struct dsa
{
	int type,nu;
	long long val;
};
dsa a[400010];
dsa c[400010];
int num=0,cnt=0;
long long read()
{
	char ch;
	long long aqq=0,f=1;
	ch=getchar();
	while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9') {aqq=aqq*10+int(ch-48);ch=getchar();}
	return aqq*f;
}
void solve(int l,int r)//套板子
{
	if (l==r) return ;
	int mid=(l+r)/2;
	solve(l,mid);solve(mid+1,r);
	int ll=l,rr=mid+1,k=0;
	long long sum=0;//这个还是统计贡献
	while (ll<=mid&&rr<=r)
	{
		if (a[ll].nu<=a[rr].nu){
			if (a[ll].type==1)
			sum+=a[ll].val;
			//如果是类型1它就有贡献,否则它就没啥用
			c[++k]=a[ll++];
	    }
	    else {//和上一个问题很像的啊
	    //位置比它前面的都已经统计过贡献了,在这次合并中它已经完成了使命(接受完了洗礼)
	    	if (a[rr].type==2)  ans[a[rr].val]+=sum;
	    	if (a[rr].type==3)  ans[a[rr].val]-=sum;
	    	c[++k]=a[rr++];
	    }
    }
    while (ll<=mid) c[++k]=a[ll++];//这个时候它的贡献已经没人要了,不统计也罢
    while (rr<=r) {//类似的啦
    	if (a[rr].type==2) ans[a[rr].val]+=sum;
    	if (a[rr].type==3) ans[a[rr].val]-=sum;
    	rr++;
    }
    for (int i=1;i<=k;i++)
      a[l+i-1]=c[i];
}
int main()
{
	freopen("shulie.in","r",stdin);
	freopen("shulie.out","w",stdout);
	n=read();
    for (int i=1;i<=n;i++)
      a[++num].val=read(),a[num].type=1,a[num].nu=i;
    m=read();
    for (int i=1;i<=m;i++)
    {
    	char ch;
    	ch=getchar();
        while (ch<'A'||ch>'Z') ch=getchar();
		if (ch=='A') {
			a[++num].type=1;a[num].nu=read();a[num].val=read();
	    }//操作1
	    else {
	    	a[++num].type=3;a[num].nu=read()-1;a[num].val=++cnt;
	    	a[++num].type=2;a[num].nu=read();a[num].val=cnt;
	    }//操作2,3它们总是成对出现,并且它们所对应的询问其实是一样的
    }
    solve(1,num);
	for (int i=1;i<=cnt;i++)
	  printf("%lld\n",ans[i]);
}

不知道为什么换一道题,临时数组名也换了。。。

三维偏序

陌上花开

链接:bzoj3262
陌上花开,可缓缓归矣
当你用排序搞了第一维,用cdq分治搞了第二维,却还要第三维,这个时候就需要用数据结构啦,例如美妙的树状数组(当然如果你想再来一遍cdq也是可以的,常数可能不怎么优秀).
还有就是这道题的问题我觉得怪怪的(语文。。。),但其实它很正常,所以请认真读题。
此题可能存在若干朵花三个属性完全一样,但是它们都认为自己比这些明明相同的花美丽(注意到=号了吗)。
所以首先我将完全一样的花并在了一起,否则不好统计。
这些细节处理,就不赘述了
代码参考(不要吐槽压行&码风)

#include <bits/stdc++.h>
using namespace std;
int n,m,num=0,ans[100010],anss[100010],ss[100010],g[200010],b[200010];
struct dsa{int aa,bb,cc,id,s;} a[100010],c[100010];
bool cmp(dsa p,dsa q) {
    return p.aa<q.aa||(p.aa==q.aa&&p.bb<q.bb)||(p.aa==q.aa&&p.bb==q.bb&&p.cc<q.cc);
 }
int lowbit(int now){return now&(-now);}
void add(int now,int T,int x) { 
    for (;now<=m;now+=lowbit(now)) 
        if (b[now]<T) g[now]=x,b[now]=T;else g[now]+=x;
 }
int find(int now,int T){
	int an=0;
	for (;now>0;now-=lowbit(now))
	  if (b[now]==T) an+=g[now];
	return an;
}
void solve(int l,int r)
{
	if (l==r) return;
	int mid=(l+r)/2;
	solve(l,mid); solve(mid+1,r);
	int ll=l,rr=mid+1,k=0;
	num++; 
	while (ll<=mid&&rr<=r) 
	if (a[ll].bb<=a[rr].bb) c[++k]=a[ll],add(a[ll].cc,num,a[ll].s),ll++; 
	else ans[a[rr].id]+=find(a[rr].cc,num),c[++k]=a[rr],rr++;
	while (ll<=mid) {c[++k]=a[ll];ll++;}
	while (rr<=r) {ans[a[rr].id]+=find(a[rr].cc,num);c[++k]=a[rr];rr++;}
	for (int i=1;i<=k;i++)
	  a[l+i-1]=c[i];  
}
int main()
{
	freopen("flower.in","r",stdin);
	freopen("flower.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) 
	scanf("%d%d%d",&a[i].aa,&a[i].bb,&a[i].cc),a[i].s=1;
	sort(a+1,a+n+1,cmp); 
	int sum=0;
	for (int i=1;i<=n;i++) 
	   if (a[i].aa==a[i+1].aa&&a[i].bb==a[i+1].bb&&a[i].cc==a[i+1].cc)   sum++,a[i].aa=a[i].bb=a[i].cc=1e9; 
	   else a[i].s+=sum,sum=0;
    sort(a+1,a+n+1,cmp);
    int N=n;
    while (a[n].aa==1e9) n--;
    for (int i=1;i<=n;i++) a[i].id=i,ss[i]=a[i].s;
    solve(1,n);
	for (int i=1;i<=n;i++) ans[i]+=ss[i]-1;
	for (int i=1;i<=n;i++) anss[ans[i]]+=ss[i];
	for (int i=0;i<N;i++)  printf("%d\n",anss[i]);
	return 0;
}

忽略那些细节,只有树状数组也就是统计贡献的差别,树状数组的关键字是第三维,类似的只有前两维都要小的时候它才可能有贡献,但对于一个在(mid+1,r)区间的数来讲,它得到的应是当前已经统计的数中第三维也比它小的数,树状数组balabala。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值