首页 > 代码库 > BZOJ 2877 NOI2012 魔幻棋盘 二维线段树

BZOJ 2877 NOI2012 魔幻棋盘 二维线段树

题目大意:给定一个矩阵,支持两种操作:

1.将某个子矩阵中的每个值增加一个数

2.询问某个子矩阵中的所有数的GCD 已知所有询问恒过定点(x,y)

算了BZOJ没有原题我还是把原题发上来吧- -

技术分享

技术分享

《论代码长度与注释长度以及题目简单程度的比例失调关系以及日本饮用水资源的解决方案》

《10K+代码是怎样炼成的》

《GCD与修改标记的正确用法》

《出题人我*你吗系列》

《懒惰即美德》

咳咳。

首先我们可以维护一个二维线段树支持子矩阵修改和子矩阵查询

但是我们很快就会发现这是不正确的 因为GCD(x+k,y+k)!=k+GCD(x,y)

由于GCD与修改标记不能相容 因此我们的数据结构无法支持区间修改区间查询

注意到GCD(x1,x2,...,xn)=GCD(x1,x2-x1,x3-x2,...,xn-x_n-1)

如果是一维的数组,我们可以维护一个差分,询问[x,y]时就去线段树上查询[x+1,y]的GCD,然后与a[x]取GCD就是答案

因此我们可以维护一个差分后的矩阵 这样就可以单点修改区间查询了

我一开始的做法是对左上角进行差分

技术分享

这样对三个区域分别维护三种线段树,询问时将询问拆成四块就行了

但是当我写完了8KB+的四个线段树的代码之后,我发现图中的橙色区域无论如何也避免不了区间修改区间查询。。。

于是8KB的代码。。。全废了。。。技术分享

重新看题,我们发现点(x,y)永远在要询问的区域内 那么我们可以以(x,y)为中心进行差分 这样图中的绿色和橙色区域都是固定的 询问也没有必要拆分了

技术分享

图中红色方块为(x,y),我们首先横向向中心差分,然后纵向向中心差分,那么就得到了最右侧的矩阵

那么询问时由于e这个值没变,因此我们直接去线段树上找到相应子矩阵的GCD就是原矩阵相应子矩阵的GCD

现在就是修改的问题 我们可以将矩阵以(x,y)为原点建系 将矩阵划分为九个区域 然后讨论修改矩阵的四个角四个边和整个位置与这九个区域的关系

技术分享

图中红色区域表示+,绿色区域表示-

举个栗子,如果一个矩阵的左上角在第一象限,就对(x1-1,y1)这个点减掉修改的值

如果一个矩阵跨越了纵轴并且下边界在原点下方,就对(x,y2+1)这个点减掉修改的值

如果一个矩阵包含原点,就对(x,y)这个点加上修改的值

等号讨论的部分欢迎参见代码 最下方长得像逻辑电路的那一坨就是

此外就是线段树第一维单点修改的时候非叶节点不能直接改 要找到两个儿子的相应位置的值并合并

终于写完了。。。我が人生に、一片の悔いなし。。。

#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define M 500500
using namespace std;
int m,n,q;

struct Array{
	long long memory[M];
	long long* operator [] (int x)
	{
		return &memory[(x-1)*n];
	}
}a;

long long GCD(long long x,long long y)
{
	return y?GCD(y,x%y):x;
}

inline long long Abs(long long x)
{
	return x<0?-x:x;
}

struct Segtree{
	Segtree *ls,*rs;
	long long val;
	Segtree():ls(0x0),rs(0x0),val(0ll) {}
	friend void Build_Tree(Segtree* &p,int x,int y,long long a[])
	{
		int mid=x+y>>1;
		p=new Segtree;
		if(x==y)
		{
			p->val=a[mid];
			return ;
		}
		Build_Tree(p->ls,x,mid,a);
		Build_Tree(p->rs,mid+1,y,a);
		p->val=GCD(p->ls->val,p->rs->val);
		//普通线段树
	}
	void Modify(int x,int y,int pos,long long val)
	{
		int mid=x+y>>1;
		if(x==y)
		{
			this->val+=val;
			return ;
		}
		if(pos<=mid) ls->Modify(x,mid,pos,val);
		else rs->Modify(mid+1,y,pos,val);
		this->val=GCD(ls->val,rs->val);
	}
	long long Get_Ans(int x,int y,int l,int r)
	{
		int mid=x+y>>1;
		if(x==l&&y==r)
			return val;
		if(r<=mid) return ls->Get_Ans(x,mid,l,r);
		if(l>mid) return rs->Get_Ans(mid+1,y,l,r);
		return GCD( ls->Get_Ans(x,mid,l,mid) , rs->Get_Ans(mid+1,y,mid+1,r) );
	}
	friend void Union(Segtree* &p,Segtree *p1,Segtree *p2,int x,int y)
	{
		int mid=x+y>>1;
		if(!p) p=new Segtree;
		if(x==y)
		{
			p->val=GCD(p1->val,p2->val);
			return ;
		}
		Union(p->ls,p1->ls,p2->ls,x,mid);
		Union(p->rs,p1->rs,p2->rs,mid+1,y);
		p->val=GCD(p->ls->val,p->rs->val);
	}
};

struct abcd{
	abcd *ls,*rs;
	Segtree *tree;
	abcd():ls(0x0),rs(0x0),tree(0x0) {}
	friend void Build_Tree(abcd* &p,int x,int y,Array& a)
	{
		int mid=x+y>>1;
		p=new abcd;
		if(x==y)
		{
			Build_Tree(p->tree,1,n,a[mid]);
			return ;
		}
		Build_Tree(p->ls,x,mid,a);
		Build_Tree(p->rs,mid+1,y,a);
		Union(p->tree,p->ls->tree,p->rs->tree,1,n);
	}
	void Modify(int x,int y,int pos1,int pos2,long long val)
	{
		int mid=x+y>>1;
		if(x==y)
		{
			tree->Modify(1,n,pos2,val);
			return ;
		}
		if(pos1<=mid) ls->Modify(x,mid,pos1,pos2,val);
		else rs->Modify(mid+1,y,pos1,pos2,val);
		long long lval=ls->tree->Get_Ans(1,n,pos2,pos2);
		long long rval=rs->tree->Get_Ans(1,n,pos2,pos2);
		long long this_val=tree->Get_Ans(1,n,pos2,pos2);
		tree->Modify(1,n,pos2,GCD(lval,rval)-this_val);
	}
	long long Get_Ans(int x,int y,int l1,int r1,int l2,int r2)
	{
		int mid=x+y>>1;
		if(x==l1&&y==r1)
			return tree->Get_Ans(1,n,l2,r2);
		if(r1<=mid) return ls->Get_Ans(x,mid,l1,r1,l2,r2);
		if(l1>mid) return rs->Get_Ans(mid+1,y,l1,r1,l2,r2);
		return GCD( ls->Get_Ans(x,mid,l1,mid,l2,r2) , 
					rs->Get_Ans(mid+1,y,mid+1,r1,l2,r2) );
	}
}*root;

int main()
{
	
	#ifndef ONLINE_JUDGE
	freopen("2877.in","r",stdin);
	freopen("2877.out","w",stdout);
	#endif
	
	int i,j,x,y,p,x1,y1,x2,y2;
	long long val;
	cin>>m>>n>>x>>y>>q;
	for(i=1;i<=m;i++)
		for(j=1;j<=n;j++)
			#ifdef ONLINE_JUDGE
				scanf("%lld",&a[i][j]);
			#else
				scanf("%I64d",&a[i][j]);
			#endif
	for(i=1;i<=m;i++)
	{
		for(j=1;j<y;j++)
			a[i][j]-=a[i][j+1];
		for(j=n;j>y;j--)
			a[i][j]-=a[i][j-1];
	}
	for(j=1;j<=n;j++)
	{
		for(i=1;i<x;i++)
			a[i][j]-=a[i+1][j];
		for(i=m;i>x;i--)
			a[i][j]-=a[i-1][j];
	}
	Build_Tree(root,1,m,a);
	for(i=1;i<=q;i++)
	{
		scanf("%d",&p);
		if(p==0)
		{
			scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
			x1=x-x1;y1=y-y1;x2=x+x2;y2=y+y2;
			long long ans=root->Get_Ans(1,m,x1,x2,y1,y2);
			ans=Abs(ans);
			#ifdef ONLINE_JUDGE
				printf("%lld\n",ans);
			#else
				printf("%I64d\n",ans);
			#endif
		}
		else
		{
			scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
			#ifdef ONLINE_JUDGE
				scanf("%lld",&val);
			#else
				scanf("%I64d",&val);
			#endif

			if( x1<=x && y1<=y && x1-1>0 && y1-1>0 )
				root->Modify(1,m,x1-1,y1-1,val);
			else if( x1<=x && y1>y && x1-1>0 )
				root->Modify(1,m,x1-1,y1,-val);
			else if( x1>x && y1<=y && y1-1>0 )
				root->Modify(1,m,x1,y1-1,-val);
			else if( x1>x && y1>y )
				root->Modify(1,m,x1,y1,val);
			//左上端点(x1,y1)

			if( x1<=x && y2>=y && x1-1>0 && y2+1<=n )
				root->Modify(1,m,x1-1,y2+1,val);
			else if( x1<=x && y2<y && x1-1>0 )
				root->Modify(1,m,x1-1,y2,-val);
			else if( x1>x && y2>=y && y2+1<=n )
				root->Modify(1,m,x1,y2+1,-val);
			else if( x1>x && y2<y )
				root->Modify(1,m,x1,y2,val);
			//右上端点(x1,y2)

			if( x2>=x && y1<=y && x2+1<=m && y1-1>0 )
				root->Modify(1,m,x2+1,y1-1,val);
			else if( x2<x && y1<=y && y1-1>0 )
				root->Modify(1,m,x2,y1-1,-val);
			else if( x2>=x && y1>y && x2+1<=m )
				root->Modify(1,m,x2+1,y1,-val);
			else if( x2<x && y1>y )
				root->Modify(1,m,x2,y1,val);
			//左下端点(x2,y1)

			if( x2>=x && y2>=y && x2+1<=m && y2+1<=n )
				root->Modify(1,m,x2+1,y2+1,val);
			else if( x2<x && y2>=y && y2+1<=n )
				root->Modify(1,m,x2,y2+1,-val);
			else if( x2>=x && y2<y && x2+1<=m )
				root->Modify(1,m,x2+1,y2,-val);
			else if( x2<x && y2<y )
				root->Modify(1,m,x2,y2,val);
			//右下端点(x2,y2)

			if( x1<=x && x2>=x )
			{
				if( y1<=y && y1-1>0 ) root->Modify(1,m,x,y1-1,-val);
				else if( y1>y ) root->Modify(1,m,x,y1,val);
				//左端点(x,y1)
				if( y2>=y && y2+1<=n ) root->Modify(1,m,x,y2+1,-val);
				else if( y2<y ) root->Modify(1,m,x,y2,val);
				//右端点(x,y2)
			}

			if( y1<=y && y2>=y )
			{
				if( x1<=x && x1-1>0 ) root->Modify(1,m,x1-1,y,-val);
				else if( x1>x ) root->Modify(1,m,x1,y,val);
				//上端点(x1,y)
				if( x2>=x && x2+1<=m ) root->Modify(1,m,x2+1,y,-val);
				else if( x2<x ) root->Modify(1,m,x2,y,val);
				//下端点(x2,y)
			}

			if( x1<=x && x2>=x && y1<=y && y2>=y )
				root->Modify(1,m,x,y,val);
		}
	}
	return 0;
}


BZOJ 2877 NOI2012 魔幻棋盘 二维线段树