CDQ分治

CDQ

1.使用cdq分治的条件: 修改操作对询问的贡献独立,修改操作相互不影响 题目可以使用离线算法,不必强制在线(询问次数可以保存在数组)
2.cdq分治的性质: cdq分治通过对时间复杂度增加一个log来降维 cdq可以用来代替复杂的数据结构 在cdq分治中,对于划分出来的两个区间,前一个子问题需要用来解决后一个子问题。
3. cdq使用步骤: 将整个操作序列分为两个长度相等的部分。 递归处理前一部分的子问题(治1) 计算前一部分子问题的修改操作对后一部分子问题的影响(治2) 递归处理后一部分的子问题(治3)

离线,处理点对关系,常用于降维度,
归并排序(合并左右区间,逐级向上):

#include<bits/stdc++.h>
using namespace std;
 
int temp[500005],a[500005],n;
long long ans;
inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
 
void merge(int l,int r)
{
	if(l==r) return;
	int mid=(l+r)/2;
	merge(l,mid);
	merge(mid+1,r);
	int i=l,j=mid+1,p=l;
	while(i<=mid&&j<=r)
	{
		if(a[i]<=a[j])
			temp[p++]=a[i++];
		else
			ans+=(mid-i+1),//计算逆序数 
			temp[p++]=a[j++];
	}
	while(i<=mid)
		temp[p++]=a[i++];
	while(j<=r)
		temp[p++]=a[j++];
	for(int k=l;k<=r;k++)
		a[k]=temp[k];
}
 
int main()
{
	n=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	merge(1,n);
	cout<<ans;
	return 0;
}

二维偏序

P2717 寒假作业
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;

int a[N],s[N],n,k;

long long cdq(int l,int r)
{
	long long res=0;
	if(l==r) return a[l]>=0;
	int mid=(l+r)>>1;
	res+=cdq(l,mid);
	res+=cdq(mid+1,r);
	s[mid]=a[mid],s[mid+1]=a[mid+1];
	for(int i=mid-1;i>=l;i--) s[i]=s[i+1]+a[i];//左半部分求后缀和
	for(int i=mid+2;i<=r;i++) s[i]=s[i-1]+a[i];//右半部分求前缀和
	sort(s+l,s+mid+1),sort(s+mid+1,s+r+1);
	int p=l,q=r;
	while(p<=mid)
	{
		while(q>=mid+1&&s[p]+s[q]>=0) q--;
		res+=r-q;//左端点固定,符合条件的右端点数
		p++;
	}
	return res;
}

int main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		a[i]-=k;
	}
	cout<<cdq(1,n);
	return 0;
} 

P5459 [BJOI2016]回转寿司

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;

long long a[N];
int n,L,R;

long long cdq(int l,int r)
{
	long long res=0;
	if(l==r) return a[l]>=L&&a[l]<=R;
	int mid=(l+r)>>1;
	res+=cdq(l,mid);
	res+=cdq(mid+1,r);
	int head=l,tail=l-1;
	for(int i=mid+1;i<=r;i++)
	{
		while(tail+1<=mid&&a[i]-a[tail+1]>=L) tail++;//最终a[i]-a[tail]>=L
		while(head<=mid&&a[i]-a[head]>R) head++;//最终a[i]-a[head]<=R
		res+=tail-head+1;//对于每个区间右端点,可行的左端点个数
	}
	sort(a+l,a+r+1);
	return res;
}

int main()
{
	cin>>n>>L>>R;
	for(int i=1;i<=n;i++)
		scanf("%ld",&a[i]),a[i]+=a[i-1];//前缀和数组
	cout<<cdq(1,n);
	return 0;
} 

三维偏序

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

#include<cstdio>
#include<algorithm>
#define maxn 200005
using namespace std;

struct node
{
	int a,b,c,cnt,ans;
}s1[maxn],s2[maxn];
int n,m,k,mx,top,su[maxn];
int c[maxn];//树状数组

bool cmp1(node x,node y)
{
	if(x.a==y.a)
	{
		if(x.b==y.b)return x.c<y.c;
		else return x.b<y.b;
	}
	else return x.a<y.a;
}//第一维排序

bool cmp2(node x,node y)
{
	if(x.b==y.b)
	return x.c<y.c;
	else return x.b<y.b;
}//第二维排序

int lowbit(int x)
{
	return x&(-x);
}


void add(int pos,int val)
{
	for(int i=pos;i<=mx;i+=lowbit(i))
		c[i]+=val;
}

int query(int pos)
{
	int tot=0;
	for(int i=pos;i;i-=lowbit(i))
		tot+=c[i];
	return tot;
}

void cdq(int l,int r)
{
	if(l==r) return;
	int mid=(l+r)>>1;
	cdq(l,mid);
	cdq(mid+1,r);
	sort(s2+l,s2+mid+1,cmp2);
	sort(s2+mid+1,s2+r+1,cmp2);
	int i=l,j;
	for(int j=mid+1;j<=r;j++)
	{
		while(s2[j].b>=s2[i].b&&i<=mid)
		{
			add(s2[i].c,s2[i].cnt);
			i++;
		}
		s2[j].ans+=query(s2[j].c);
	}
	for(int p=l;p<i;p++)
		add(s2[p].c,-s2[p].cnt);
}

int main()
{
	scanf("%d%d",&n,&k);
	mx=k;//树状数组的区间
	for(int i=1;i<=n;++i)
		scanf("%d%d%d",&s1[i].a,&s1[i].b,&s1[i].c);
	sort(s1+1,s1+1+n,cmp1);//第一维为关键字排序
	for(int i=1;i<=n;++i)
	{
		top++;
		if(s1[i].a!=s1[i+1].a||s1[i].b!=s1[i+1].b||s1[i].c!=s1[i+1].c)
		{
			s2[++m]=s1[i];
			s2[m].cnt=top;
			top=0;
		}
	}//第一维已有序,合并相同节点
	cdq(1,m);//cdq分治
	for(int i=1;i<=m;++i)
		su[s2[i].ans+s2[i].cnt-1]+=s2[i].cnt;
	for(int i=0;i<n;++i)
	printf("%d\n",su[i]);
	return 0;
}

P3157 [CQOI2011]动态逆序对

对答案有贡献的点 ( i , j ) (i,j) i,j对满足的条件:
v a l i < v a l j , p o s i > p o s j , t i m e i < = t i m e j val_i<val_j, pos_i>pos_j, time_i<=time_j vali<valj,posi>posj,timei<=timej
那么这个问题就变成了经典的三维偏序问题,可以通过cdq分治来解决。
先对time维排序(不需要额外的处理),在time有序的情况下使得归并区间内的pos有序,将val加到树状数组中。

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
struct Node
{
	int val,pos,time,cnt;
}e[N];
bool cmp1(Node x,Node y)
{
	return x.pos<y.pos;
}
int pos[N],n,m,tot,c[N];
//树状数组在val维上建 
long long ans[N];
int read()
{
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9') ch=='-'&&(f=-1),ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}

int lowbit(int x)
{
	return x&(-x);
}

void add(int pos,int x)
{
	for(int i=pos;i<=n;i+=lowbit(i))
		c[i]+=x;
}

int query(int pos)
{
	int res=0;
	for(int i=pos;i;i-=lowbit(i))
		res+=c[i];
	return res;
}
void cdq(int l,int r)
{
	if(l==r) return;
	int mid=(l+r)>>1;
	cdq(l,mid);
	cdq(mid+1,r);
	sort(e+l,e+mid+1,cmp1);
	sort(e+mid+1,e+r+1,cmp1);//按照pos排序
	int i=l,j;//i为左部分指针,j为右部分指针 
	for(j=mid+1;j<=r;j++)
	{
		while(i<=mid&&e[i].pos<=e[j].pos)//time_i<=time_j&&pos_i<=pos_j
		{
			add(e[i].val,e[i].cnt);
			i++;
		} 
		ans[e[j].time]+=1ll*e[j].cnt*(query(n)-query(e[j].val));
		//(query(n)-query(e[j].val)):pos_i<=pos_j&&val_i>val_j形成的逆序对 
	}
	for(int p=l;p<i;p++)
		add(e[p].val,-e[p].cnt);//还原 
	i=mid;
	for(int j=r;j>mid;j--)
	{
		while(i>=l&&e[i].pos>=e[j].pos)//time_i<=time_j&&pos_i>=pos_j
		{
			add(e[i].val,e[i].cnt);
			i--;
		}
		ans[e[j].time]+=1ll*e[j].cnt*query(e[j].val-1); 
		// query(e[j].val-1):pos_i>=pos_j&&val_i<val_j形成的逆序对 
	}
	for(int p=mid;p>i;p--)
		add(e[p].val,-e[p].cnt);//还原 
}

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		int x=read();
		pos[x]=i;
		e[++tot]={x,i,0,1};
	}
	for(int i=1;i<=m;i++)
	{
		int x=read();
		e[++tot]={x,pos[x],i,-1};
	}
	//此时 time维 有序 
	cdq(1,tot);
	//此时ans[0]是原始数列逆序对数,之后的 ans[1]~ans[m]都<=0 
	for(int i=1;i<=m;i++) ans[i]+=ans[i-1];
	for(int i=0;i<m;i++) printf("%lld\n",ans[i]);
	return 0;
}

P4169 [Violet]天使玩偶/SJY摆棋子

只考虑左下角的所有点,对答案有贡献的点 ( i , j ) (i,j) i,j对满足的条件:
x i < x j , y i < y j , t i < t j x_i<x_j, y_i<y_j, t_i<t_j xi<xj,yi<yj,ti<tj
那么这个问题就变成了经典的三维偏序问题,可以通过cdq分治来解决。
先对time维排序(不需要额外的处理),在time有序的情况下使得归并区间内的x有序,将(x+y)加到y轴的树状数组中。

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
const int MAX=2e6+10;
int n,m,tot,num;
struct Node
{
	int x,y,t,type;
}e[N];

int c[MAX],maxn;
int ques[N],ans[N];

int read()
{
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9') ch=='-'&&(f=-1),ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}

bool cmp(Node a,Node b)
{
	return a.x<b.x;
}

int lowbit(int x)
{
	return x&(-x);
}

void add(int pos,int val)
{
	for(int i=pos;i<=maxn;i+=lowbit(i))
		c[i]=max(c[i],val);
}

void cls(int pos)
{
	for(int i=pos;i<=maxn;i+=lowbit(i))
		c[i]=0;
}

int query(int pos)
{
	int res=0;
	for(int i=pos;i;i-=lowbit(i))
		res=max(c[i],res);
	return res;
}

void cdq(int l,int r)
{
	if(l==r) return;
	int mid=(l+r)>>1;
	cdq(l,mid);
	cdq(mid+1,r);
	sort(e+l,e+mid+1,cmp);
	sort(e+mid+1,e+r+1,cmp);
	int i=l,j;
	for(j=mid+1;j<=r;j++)
	{
		while(i<=mid&&e[i].x<=e[j].x)
		{
			if(e[i].type==1)
			{
				//cout<<"i:"<<i<<' ';
				//cout<<e[i].x<<' '<<e[i].x+e[i].y<<endl;
				add(e[i].y,e[i].x+e[i].y);
			}
			i++;
		}
		if(e[j].type==2)
		{
			for(int k=1;k<=maxn;k++)
				cout<<" c["<<k<<"]:"<<c[k];
			cout<<endl;
			int temp=query(e[j].y);
			cout<<"query("<<e[j].y<<"):"<<temp<<endl;
			ans[e[j].t]=min(e[j].x+e[j].y-temp,ans[e[j].t]);
		}
	}
	for(int p=l;p<i;p++)
		cls(e[p].y);
}
int main()
{
	cin>>n>>m;
	memset(ans,0x3f,sizeof(ans));
	for(int i=1;i<=n;i++)
	{
		int x=read()+1,y=read()+1;
		e[++tot]={x,y,0,1};
		maxn=max(maxn,x+y);
	}
	for(int i=1;i<=m;i++)
	{
		int op=read(),x=read()+1,y=read()+1;
		e[++tot]={x,y,i,op};
		if(op==2) ques[++num]=i;
		maxn=max(maxn,x+y);
	}
	maxn++;
	cdq(1,tot);
	for(int i=1;i<=num;i++)
		printf("%d\n",ans[ques[i]]);
	return 0;
}

将点坐标对称,每次更新答案,可以求出最小值。

#include<bits/stdc++.h>
using namespace std;
const int N=6e5+10;
const int MAX=3e6+10;
int n,m,tot,num,cnt;
struct Node
{
	int x,y,t,type;
}e[N],a[N];

int c[MAX],maxn=-1e9;
//在y维上建树状数组 
int ques[N],ans[N];

int read()
{
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9') ch=='-'&&(f=-1),ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}

void init(int d)
{
    int mxx=0,myy=0;cnt=0;
    for(int i=1;i<=n+m;++i)
    {
        if(d==1) a[i].x=maxn-a[i].x;//横坐标全部对称过去 
        else if(d==2) a[i].y=maxn-a[i].y;//纵坐标全部对称过去 
        if(a[i].type==2) mxx=max(mxx,a[i].x),myy=max(myy,a[i].y);//更新边界 
    }
    for(int i=1;i<=n+m;++i)
    	if(a[i].x<=mxx&&a[i].y<=myy)//可以被询问到的点 
    		e[++cnt]=a[i];
}

bool cmp(Node a,Node b)
{
	return a.x<b.x;
}

int lowbit(int x)
{
	return x&(-x);
}

void add(int pos,int val)
{
	for(int i=pos;i<=maxn;i+=lowbit(i))
		c[i]=max(c[i],val);
}

void cls(int pos)
{
	for(int i=pos;i<=maxn;i+=lowbit(i))
		c[i]=0;
}

int query(int pos)
{
	int res=0;
	for(int i=pos;i;i-=lowbit(i))
		res=max(c[i],res);
	return res;
}

void cdq(int l,int r)
{
	if(l==r) return;
	int mid=(l+r)>>1;
	cdq(l,mid);
	cdq(mid+1,r);
	sort(e+l,e+mid+1,cmp);//左半部分按x排序 
	sort(e+mid+1,e+r+1,cmp);//右半部分按x排序 
	int i=l,j;
	for(j=mid+1;j<=r;j++)
	{
		while(i<=mid&&e[i].x<=e[j].x)
		{
			if(e[i].type==1)
				add(e[i].y,e[i].x+e[i].y);//更新区域最大值 
			i++;
		}
		if(e[j].type==2)
		{
			//for(int k=1;k<=maxn;k++)
			//	cout<<" c["<<k<<"]:"<<c[k];
			//cout<<endl;
			int temp=query(e[j].y);//查询0~y之间的区域最大值 
			//cout<<"query("<<e[j].y<<"):"<<temp<<endl;
			if(temp) ans[e[j].t]=min(e[j].x+e[j].y-temp,ans[e[j].t]);
		}
	}
	for(int p=l;p<i;p++)
		if(e[p].type==1) cls(e[p].y);//还原,清零 
}

int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;i++)
	{
		int x=read()+1,y=read()+1;//题给数据有0 
		a[++tot]={x,y,0,1};//初始t==0 
		maxn=max(maxn,x);
		maxn=max(maxn,y);
	}
	for(int i=1;i<=m;i++)
	{
		int op=read(),x=read()+1,y=read()+1;
		a[++tot]={x,y,i,op};
		if(op==2) ques[++num]=i,ans[i]=1e9;//储存下询问 
		maxn=max(maxn,x);
		maxn=max(maxn,y);
	}
	maxn++;
	//此时t维已经有序 
	init(0); cdq(1,cnt);
    init(1); cdq(1,cnt);
    init(2); cdq(1,cnt);
    init(1); cdq(1,cnt);
	for(int i=1;i<=num;i++)
		printf("%d\n",ans[ques[i]]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

春弦_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值