cdq分治

/*
cdq分治模板 程序为例题题解 
例题:  来源:G:\苏畅\其他\c++\程序保存\石门中学创新班\2017.8 暑假夏令营\2017.8.8\讲课 分治杂谈.ppt
你有一个N*N的棋盘,每个格子内有一个整数,初始时的时候全部为0,现在需要维护两种操作共M次:
1、将格子(x,y)里的数字加上d。操作:A x y d 
2、输出(x1,y1)为左上角,(x2,y2)为右下角的矩形内数字和。操作:Q x1 y1 x2 y2 
N,M<=500000
题解:将每次询问分解成2个,对1到x1-1区间y1到y2的询问和对1到x2区间y1到y2的询问。 
对时间分治,考虑前面的操作对后面询问的贡献。 
然后变成操作全部在前面,询问全部在后面 
对修改和询问按照x排序,再次利用时间消去x这个限制,开树状数组以y为下标存贮修改值,
遇见操作则加,遇见询问则取y1到y2之和,因为已经按照x排序了,所以前面加入的修改必然对询问产生贡献
最后递归求解 
注意:见整体二分注意事项 
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

const int maxn = 500000 + 100;

struct op{
	int x,y1,y2,d,t,i;//如果是修改,x为横坐标,y为纵坐标,d为修改值 ,t表示时间 
	bool ask;// 如果是查询,x是横坐标,y1是纵坐标1,y2是纵坐标2 ,d是当前答案,t表示时间 i表示第i个问题 
}q[maxn*2],q1[maxn*2],q2[maxn*2]; 

struct tree{
	int a[maxn];
	void add(int x,int d){for(;x<maxn;x+=x&-x) a[x]+=d;}
	int qsum(int x){int p=0;for(;x>=1;x-=x&-x) p+=a[x];return p;}
	int sum(int l,int r){return qsum(r)-qsum(l-1);}
}t;

int n,m,qn,tot;
int p[maxn];

void read()
{
	int i,x1,x2,y1,y2,d;
	char c;
	
	cin>>n>>m;qn=tot=0;
	for(i=0;i<m;i++)
	{
		cin>>c;
		if(c=='A')
		{
			cin>>x1>>y1>>d;
			q[tot++]= (op){x1,y1,0,d,i,0,false};
		}
		else
		{
			cin>>x1>>y1>>x2>>y2;
			if(x1!=1) q[tot++]= (op){x1-1,y1,y2,0,i,qn,true};
			q[tot++]= (op){x2,y1,y2,0,i,qn,true};
			qn++;
		}
	}
}

void solve(int ql,int qr,int l,int r)//lr表示当前时间的左右区间,qlqr表示操作序列 
{
	//cout<<l<<' '<<r<<endl;
	int i,mid,k1,k2;
	if(ql>qr) return;
	if(l==r)
	{
		for(i=ql;i<=qr;i++)
		{
			if(q[i].ask)
			{
				int cnt=t.sum(q[i].y1,q[i].y2);
				q[i].d+=cnt; 
			}
			else t.add(q[i].y1,q[i].d);
		}
		
		for(i=ql;i<=qr;i++) if(!q[i].ask) t.add(q[i].y1,-q[i].d);
		return;
	}
	
	mid=(l+r)>>1;k1=k2=0;
	for(i=ql;i<=qr;i++)
	{
		if(q[i].ask)
		{
			if(q[i].t<=mid) q1[k1++]=q[i];
			else
			{
				int cnt=t.sum(q[i].y1,q[i].y2);
				q[i].d+=cnt; 
				q2[k2++]=q[i]; 
			}
		}
		else
		{
			if(q[i].t<=mid) t.add(q[i].y1,q[i].d),q1[k1++]=q[i];
			else            q2[k2++]=q[i];
		}
	}
	
	for(i=0;i<k1;i++) if(!q1[i].ask) t.add(q1[i].y1,-q1[i].d);
	
	for(i=ql;i<ql+k1;i++) q[i]=q1[i-ql];
	for(i=ql+k1;i<=qr;i++) q[i]=q2[i-ql-k1];
	
	solve(ql,ql+k1-1,l,mid);
	solve(ql+k1,qr,mid+1,r);
}

bool cmpx(op aa,op bb){return aa.x==bb.x?aa.t<bb.t:aa.x<bb.x;}
bool cmpt(op aa,op bb){return aa.t==bb.t?aa.x<bb.x:aa.t<bb.t;}

void print()
{
	int i,j;
	
	
	for(i=0;i<tot;i++) if(q[i].ask)
	{
		if(p[q[i].i])
		{
			p[q[i].i]=-p[q[i].i];
			p[q[i].i]+=q[i].d;
		}
		else p[q[i].i]+=q[i].d;
	}
	
	for(i=0;i<qn;i++) cout<<p[i]<<endl;
}

int main()
{
	freopen("1.in","r",stdin);
	freopen("1.out","w",stdout);
	
	read();
	sort(q,q+tot,cmpx);//一开始先按照x排序,这样就可以消除x这个限制 
	solve(0,tot-1,0,m-1);
	sort(q,q+tot,cmpt);
	print();
	
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值