POJ1195/BZOJ1176 题解,CDQ分治

POJ1195 MOBILE/ BZOJ 1176

这两个题题意完全相同,放在一起说吧

题意:

你有一个N*N的棋盘,每个格子内有一个整数,初始时的时候全部为0,现在需要维护两种操作:

命令

参数限制

内容

1 x y A

1<=x,y<=N,A是正整数

将格子x,y里的数字加上A

2 x1 y1 x2 y2

1<=x1<= x2<=N

1<=y1<= y2<=N

输出x1 y1 x2 y2这个矩形内的数字和

3

终止程序

标准的三维偏序问题,三个维度分别为x,y和时间t。

在POJ1195中,N<=1024,可以用二维树状数组来做,非常简单。

但在BZOJ1176中,N<=500000,空间开不下,所以用CDQ分治+一维树状数组来做。

对于所有的查询操作,我们拆分成4个。分别查询(0,0)到(x1-1,y1-1),(x2,y2),(x2,y1-1),(x1-1,y2)四个矩形的数字和

然后用容斥原理加减一下就可以算出(x1,y1),(x2,y2)的矩形数字和了。

CDQ分治中,首先按x来排序,对t这个维度进行分治。

那么t<=mid的更新操作都能影响到t'>=mid+1的查询结果。

所以在CDQ(l,r)中,按照x从小到大扫一遍所有操作,遇到更新操作就在树状数组上插入y值,

遇到查询操作就给该操作的结果加上树状数组上查询到的sum(1,y)的值。

接下来用类似归并排序的方法,把t<=mid的操作都放到数组左半部分,t>=mid+1的操作都放到右半部分

这么做之后两半部分的x依然是有序的,就可以递归处理两半部分之间的更新操作对查询操作产生的影响了。

下面是代码,按照POJ1195的输入格式写的

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<string>
#define ll long long
using namespace std;
int n;
int tree[1029];
int cnt=0;
int anscnt=0;
int ans[60111]; 
struct operation
{
	int x,y;
	int k;
	int id,ansid; 
	bool type;//0,add;1,query 
	operation(int x_,int y_,int k_,bool type_,int id_):
		x(x_),y(y_),k(k_),type(type_),id(id_){}
	operation(int x_,int y_,int k_,int ansid_,bool type_,int id_):
		x(x_),y(y_),k(k_),ansid(ansid_),type(type_),id(id_){}
	operation(){}
	bool operator <(const operation& rhs)const
	{
		if(x==rhs.x&&y==rhs.y)return type<rhs.type;
		if(x==rhs.x)return y<rhs.y;
		return x<rhs.x;
	}
}op[241111],temp[241111];
void add(int x,int k)
{
	for(x;x<=n;x+=x&-x)
		tree[x]+=k;
}
int query(int x)
{
	int res=0;
	for(x;x>0;x-=x&-x)
		res+=tree[x];
	return res;
}
void cdq(int l,int r)
{
	//cout<<l<<" "<<r<<endl;
	if(l==r)return;
	int mid=l+r>>1,lp=l,rp=mid+1;
	for(int i=l;i<=r;++i)
	{
		if(op[i].id<=mid&&!op[i].type)
			add(op[i].y,op[i].k);
		if(op[i].id>mid&&op[i].type)
			ans[op[i].ansid]+=query(op[i].y)*op[i].k;
	}
	for(int i=l;i<=r;++i)
		if(op[i].id<=mid&&!op[i].type)
			add(op[i].y,-op[i].k);
	for(int i=l;i<=r;++i)
		if(op[i].id<=mid)
			temp[lp++]=op[i];
		else temp[rp++]=op[i];
	for(int i=l;i<=r;++i)
		op[i]=temp[i];
	cdq(l,mid);cdq(mid+1,r);
}
int main()
{
	int o;
	while(scanf("%d",&o),o!=3)
	{
		int x,y,k,x2,y2;
		if(o==0)scanf("%d",&n);
		if(o==1)
		{
			scanf("%d%d%d",&x,&y,&k);
			++cnt;op[cnt]=operation(x+1,y+1,k,0,cnt);
		}else if(o==2){
			scanf("%d%d%d%d",&x,&y,&x2,&y2);
			anscnt++;
			++cnt;op[cnt]=operation(x2+1,y2+1,1,anscnt,1,cnt);
			++cnt;op[cnt]=operation(x,y2+1,-1,anscnt,1,cnt);
			++cnt;op[cnt]=operation(x2+1,y,-1,anscnt,1,cnt);
			++cnt;op[cnt]=operation(x,y,1,anscnt,1,cnt);
		}
	}
	sort(op+1,op+cnt+1);
	cdq(1,cnt);
	for(int i=1;i<=anscnt;++i)
		printf("%d\n",ans[i]);
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值