cdq分治学习小结

一脸懵逼的学了好久,应该算学会了吧。基本了解cdq分治的思想,然而代码....一如既往的丑。

cdq分治只支持离线操作,主要作用是降维,反正就是大大优化了时间和空间。(感觉不扯几句作用非常不好)。

 

二维偏序

题目描述

输入长度为n的序列,并进行m次操作,操作分两种

1.将编号为的数加上y(1 x y)

2.求出某区间每一个数的和(2 x y)

典型的树状数组题,但我们可以把它看做一个二维偏序问题。这个问题一维是时间二维是每个查询或者修改的数的位置。

因为我们是按时间顺序输入的,所以我们默认时间有序。

对于左右两个区间合并,我们只执行左区间的修改以及右区间的查询。

#include<cstdio>
#include<cstring>
using namespace std;
#define N 500000+2000
int n,m,tot,ai;
int a[2*N];
struct node
{
	int pos,opr,val;
}q[3*N],tmp[3*N];
void cdq(int l,int r)
{
	if(l==r)
		return;
	int mid=(l+r)/2,ti=0,i=l,j=mid+1,cnt=0;
	cdq(l,mid),cdq(mid+1,r);
	while(i<=mid&&j<=r)
	{
		if(q[i].pos<=q[j].pos)
		{
			if(q[i].opr==1)
				cnt+=q[i].val;
			tmp[++ti]=q[i];
			i++;
		}
		else
		{
			if(q[j].opr==2)
				a[q[j].val]-=cnt;
			if(q[j].opr==3)
				a[q[j].val]+=cnt;
			tmp[++ti]=q[j];
			j++;
		}
	}
	while(i<=mid)
		tmp[++ti]=q[i],i++;
	while(j<=r)
	{
		if(q[j].opr==2)
			a[q[j].val]-=cnt;
		if(q[j].opr==3)
			a[q[j].val]+=cnt;
		tmp[++ti]=q[j];
		j++;
	}
	for(int k=1;k<=ti;k++)
		q[k+l-1]=tmp[k];
}
int main()
{

	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		q[++tot].pos=i,q[tot].opr=1,q[tot].val=x;
	}
	for(int i=1;i<=m;i++)
	{
		int x,y,op;
		scanf("%d %d %d",&op,&x,&y);
		if(op==1)
		{
			q[++tot].pos=x,q[tot].opr=1,q[tot].val=y;
		}
		else
		{
			q[++tot].pos=x-1,q[tot].opr=2,q[tot].val=++ai;
			q[++tot].pos=y,q[tot].opr=3,q[tot].val=ai;
		}
	}
	cdq(1,tot);
	for(int i=1;i<=ai;i++)
		printf("%d\n",a[i]);
}

三维偏序

题目描述

有 n 个元素,第 i 个元素有 ai​ 、 bi​ 、 ci​ 三个属性,设 f(i) 表示满足 aj​≤ai​ 且 bj​≤bi​ 且 cj​≤ci​ 的 j 的数量。

对于d∈(0,n) ,求f(i)=d 的数量

输入输出格式

输入格式:

第一行两个整数 n 、k ,分别表示元素数量和最大属性值。

之后 n 行,每行三个整数ai​ 、bi​ 、ci​ ,分别表示三个属性值。

输出格式:

输出 n 行,第d+1 行表示f(i)=d 的 i 的数量。

样例输入

10 3
3 3 3
2 3 3
2 3 1
3 1 1
3 1 2
1 3 1
1 1 2
1 2 2
1 3 2
1 2 1
3
1
3
0
1
0
1
0
0
1

典型的cdq三维分治问题,直接判断每一维的数的大小累加答案即可。
但要注意,相同的元素取位置最往后的那个,以确保答案全部累加。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100000+200
#define lowbit(x) x&(-x)
struct node
{
	int xi,yi,zi,pos,val;
}q[N],tmp[N],tmpp[N];
int n,mxt;
int f[N],ans[N],same[N],tree[2*N];
void add(int x,int ci)
{
	while(x<=mxt)
		tree[x]+=ci,x+=lowbit(x);
}
int query(int x)
{
	int cnt=0;
	while(x)
		cnt+=tree[x],x-=lowbit(x);
	return cnt;
}
int cmp(node a,node b)
{
	if(a.xi==b.xi)
	{
		if(a.yi==b.yi)
			return a.zi<b.zi;
		return a.yi<b.yi;
	}
	return a.xi<b.xi;
}
void cdq1(int l,int r)
{
	for(int i=l;i<=r;i++)
	{
		if(tmp[i].val==0)
			add(tmp[i].zi,1);
		if(tmp[i].val==1)
			f[tmp[i].pos]+=query(tmp[i].zi);
	}
	for(int i=l;i<=r;i++)
		if(tmp[i].val==0)
			add(tmp[i].zi,-1);
}
void cdq(int l,int r)
{
	if(l==r)
		return;
	int mid=(l+r)/2,i=l,j=mid+1,ti=l-1;
	cdq(l,mid),cdq(mid+1,r);
	while(i<=mid&&j<=r)
	{
		if(q[i].yi<=q[j].yi)
			tmp[++ti]=q[i++],tmp[ti].val=0;
		else
			tmp[++ti]=q[j++],tmp[ti].val=1;
	}
	while(i<=mid)
		tmp[++ti]=q[i++],tmp[ti].val=0;
	while(j<=r)
		tmp[++ti]=q[j++],tmp[ti].val=1;
	for(int k=l;k<=r;k++)
		q[k]=tmp[k];
	cdq1(l,r);
}
int main()
{
	
	scanf("%d %d",&n,&mxt);
	for(int i=1;i<=n;i++)
	{
		scanf("%d %d %d",&q[i].xi,&q[i].yi,&q[i].zi);
		q[i].pos=i;
	}
	sort(q+1,q+n+1,cmp);
	for(int i=1;i<=n;)
	{
		int j=i+1;
		while(j<=n&&q[i].xi==q[j].xi&&q[i].yi==q[j].yi&&q[i].zi==q[j].zi)
			j++;
		while(i<j)
            same[q[i++].pos]=q[j-1].pos;
	}
	cdq(1,n);
	for(int i=1;i<=n;i++)
		ans[f[same[q[i].pos]]+1]++;
	for(int i=1;i<=n;i++)
		printf("%d\n",ans[i]);
}

四维偏序 

jzoj4419. hole

Description

GFS打算去郊外建所别墅,享受生活,于是他耗费巨资买下了一块风水宝地,但令他震惊的是,一群DSJ对GFS的富贵生活深恶痛绝,决定打洞以搞破坏。
现在我们简化一下这个问题,在这片土地上会按顺序发生一系列事件。
①一只DSJ在(x,y) 这个点打了一个洞。
②有着高雅品味GFS想建一个等腰直角三角形的别墅,即由(x,y) ,(x+d,y) ,(x,y+d) 三点围成的三角形,但为了地基的牢固,他想知道当前这块三角形土地内的洞的个数。
GFS现在对DSJ已经忍无可忍了,请你帮他回答这些询问。
初始土地上没有洞。GFS毕竟是GFS,你可以认为土地无限大。

Input

第一行一个整数 n,表示事件数。接下来n行,每行3个非负整数x ,y ,d 。
d=0 表示DSJ打洞的事件。否则表示GFS建房的询问。

Output

对每个询问输出一个整数,表示当时询问的三角形内的洞的个数。

Sample Input/Sample Output

8
1 3 0
1 5 0
3 6 0
4 4 0
2 6 0
1 5 3
1 5 4
1 1 1

 

3
3
0
4
1 5 0
3 7 0
2 5 6
2 3 4
1
0

Data Constraint

30%的数据n<=3333 。
另30% 的数据 GFS只会在DSJ打完洞后才开始询问,xi,yi<=333333 。
100%的数据 1<=n<=88888,xi,yi<=3333333 。

solution

这道题好像还有其他做法,我用的是最蠢的cdq分治,并且由于代码丑开了O2才卡过去。

首先看大佬题解的一张图。

假设蓝色区域是我们要求的地方,那么这个区域可以用夹在两条深蓝色的线的所有点减去两个平行四边形来表示。

很明显这道题的四维分别是时间t,横坐标x,纵坐标y,以及第四维(x+y),表示每一个等腰三角形的底,也就是图上的斜线。

时间默认有序,第二维cdq,第三位cdq,第四维用树状数组维护。

#pragma GCC optimize (2)
#pragma G++ optimize (2)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 400000
#define M 3333333+200
#define lowbit(x) x&(-x)
struct node
{
	long long xi,yi,di,frm,val,vall,opr;
}q[2*N],tmp[2*N],tmpp[2*N];
long long n,tot,ast,mtx;
long long tree[5*M],ans[N];
void add(long long x,long long ci)
{
	while(x<=mtx)
		tree[x]+=ci,x+=lowbit(x);
}
long long query(long long x)
{
	long long cnt=0;
	while(x)
		cnt+=tree[x],x-=lowbit(x);
	return cnt;
}
void cdq2(long long l,long long r)
{
	for(long long i=l;i<=r;i++)
	{
		if((tmpp[i].vall==0&&tmpp[i].val==0)&&tmpp[i].opr==1)
			add(tmpp[i].di,1);
		if(tmpp[i].vall==1&&tmpp[i].val==1&&tmpp[i].opr==2)
			ans[tmpp[i].frm]-=query(tmpp[i].di);
		if(tmpp[i].vall==1&&tmpp[i].val==1&&tmpp[i].opr==3)
			ans[tmpp[i].frm]+=query(tmpp[i].di);
	}
	for(long long i=l;i<=r;i++)
		if((tmpp[i].vall==0&&tmpp[i].val==0)&&tmpp[i].opr==1)
			add(tmpp[i].di,-1);
}
void cdq1(long long l,long long r)
{
	if(l==r)
		return;
	long long mid=(l+r)/2,i=l,j=mid+1,ti=l-1,cnt=0;
	cdq1(l,mid),cdq1(mid+1,r);
	while(i<=mid&&j<=r)
	{
		if(tmp[i].yi<=tmp[j].yi)
			tmpp[++ti]=tmp[i++],tmpp[ti].vall=0;
		else
			tmpp[++ti]=tmp[j++],tmpp[ti].vall=1;
	}
	while(i<=mid)
		tmpp[++ti]=tmp[i++],tmpp[ti].vall=0;
	while(j<=r)
		tmpp[++ti]=tmp[j++],tmpp[ti].vall=1;
	for(long long k=l;k<=r;k++)
		tmp[k]=tmpp[k];
	cdq2(l,r);
}
void cdq(long long l,long long r)
{
	if(l==r)
		return;
	long long mid=(l+r)/2,i=l,j=mid+1,ti=l-1,cnt=0;
	cdq(l,mid),cdq(mid+1,r);
	while(i<=mid&&j<=r)
	{
		if(q[i].xi<=q[j].xi)
			tmp[++ti]=q[i++],tmp[ti].val=0;
		else
			tmp[++ti]=q[j++],tmp[ti].val=1;
	}
	while(i<=mid)
		tmp[++ti]=q[i++],tmp[ti].val=0;
	while(j<=r)
		tmp[++ti]=q[j++],tmp[ti].val=1;
	for(long long k=l;k<=r;k++)
		q[k]=tmp[k];
	cdq1(l,r);
}
int main()
{
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	scanf("%lld",&n);
	for(long long i=1;i<=n;i++)
	{
		long long x,y,d;
		scanf("%lld %lld %lld",&x,&y,&d);
		if(d==0)
		{
			q[++tot].xi=x;
			q[tot].yi=y;
			q[tot].di=x+y;
			q[tot].opr=1;
		}
		else
		{
			
			q[++tot].xi=x-1;
			q[tot].yi=x+y+d;
			q[tot].frm=++ast;
			q[tot].opr=2;
			q[tot].di=x+y+d;
			
			q[++tot].xi=x+y+d;
			q[tot].yi=y-1;
			q[tot].frm=ast;
			q[tot].opr=2;
			q[tot].di=x+y+d;
			
			q[++tot].xi=x+y+d;
			q[tot].yi=x+y+d;
			q[tot].frm=ast;
			q[tot].opr=3;
			q[tot].di=x+y+d;
			
			q[++tot].xi=x-1;
			q[tot].yi=y-1;
			q[tot].frm=ast;
			q[tot].opr=3;
			q[tot].di=x+y-2;
		}
		mtx=max(mtx,x+y+d);
	}
	cdq(1,tot);
	for(long long i=1;i<=ast;i++)
		printf("%lld\n",ans[i]);
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值