2018-2019 ACM-ICPC Southeastern European Regional (SEERC 2018) G - Matrix Queries (level 3)(线段树)

题目链接

题意:

给你一个(2^n)*(2^n)的矩阵,矩阵元素只有0,1两种颜色。

定义一个元素的价值是

1.如果一个矩阵都是一种颜色,那么他的价值为1

2.如果一个矩阵不纯色,那么他的价值是把他分成4个(2^(k-1))*(2^(k-1))

(假设原来的大小是(2^k)*(2^k))的矩阵的价值之和+1

然后有q个操作,一开始矩阵都是0颜色,

每一次操作,你需要把t=0,把第x行都取反;t=1,把第x列取反。

然后输出当前矩阵的价值

(n<=20,q<=1e6)

解析:

这道题真的磨了2天......

一开始想了一天,想不出来,看了别人的题解

大佬题解

结论:我们称依题目给出的公式计算矩阵A的得分时递归处理到的矩阵均称为A的“子矩阵”,记这些矩阵中有n个为不“纯色”的矩阵,则矩阵A的得分为4n+1。

 那么我们然后就只要找(2^n)*(2^n)的里面有多少不纯色的矩阵就可以了。

然后剩下的就是一些套路了...(反正我是看不出来,模仿大佬的口气)

因为他这里行数达到2^n次,并且每一次都对行/列进行操作,那么我们的突破点也一定是对行/列进行的。

然后注意纯色矩阵是有一些性质的:

一个矩阵是纯色矩阵,那么里面所有行的操作次数的奇偶性一定是一样的,并且列也满足这一条件

那么我们只要找到纯色矩阵的数目,在用所有矩阵的数目-纯色=非纯色

然后假定我们要找(2^k)*(2^k)大小的纯色矩阵个数

其实就是找[1,2^n]行平分成2^(n-k)组长度为2^k的连续行区间,在这些组里面找多少组行的奇偶性相同的,

同理再用相同的方法找出列有多少组,那么两者相乘就是(2^n)*(2^n)里面有多少(2^k)*(2^k)大小的纯色矩阵。

求法就是用线段树来维护,因为这里行永远是2^n,所以建出来的线段树永远是满二叉树。

线段树每一个节点代表,该区间内有多少节点颜色为1,那么对于求有多少组长度为2^k的连续行区间奇偶相同,

就只需要判断第n-k层有多少点值为0/2^k

最后更新的时候,同时更新一遍答案数组。这里我又卡了一天。写了超多线段树都T了,

后来看大佬的代码,发现每一次更新,只有线段树上路径上的点的价值会改变,即各层的点只会有一个点答案会+1/-1/不变。

那么在更新线段树的同时,更新这一层的答案就可以了。

#include 
#include 
#include 

using namespace std;
typedef long long ll;
const int N = (1<<20)+100;
int Btree[N*4][2];
//int status[N*8][2];
int bit[31];
ll four[21];
int mat[2][N];
int n;
ll valz[2][N];

void updateone(int root,int l,int r,int index,int val,int rc,int dep)
{
	if(l>r)return;
	if(l==r)
	{
		if(l==index)
		{
			Btree[root][rc]+=val;
		}
		return;
	}
	if(Btree[root][rc]==bit[n-dep]||Btree[root][rc]==0)
		valz[rc][n-dep]--;

	int mid=(l+r)/2;
	if(index<=mid)
		updateone(root*2,l,mid,index,val,rc,dep+1);
	else 
		updateone(root*2+1,mid+1,r,index,val,rc,dep+1);

	Btree[root][rc]=Btree[root*2][rc]+Btree[root*2+1][rc];
	if(Btree[root][rc]==bit[n-dep]||Btree[root][rc]==0)
		valz[rc][n-dep]++;
		
}




inline void read(int& x){
	char ch = getchar();
	x = 0;
	for(;ch < '0' || ch > '9';ch = getchar());
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x*10+(ch-'0');
}

int main()
{
	int q;
	read(n),read(q);
	bit[0]=1;
	
	ll sum=1;
	for(int i=1;i<=n+10;i++) bit[i]=bit[i-1]*2;
	ll four=1;
	for(int i=1;i<=n;i++)
	{
		four=four*4;
		sum+=four;
	}
	for(int i=0;i<=n;i++)
		valz[0][i]=valz[1][i]=bit[n-i];
	
	for(int i=1;i<=q;i++)
	{
		int t,x;
		read(t),read(x);
		int val=1;
		if(mat[t][x]==1) val=-1;
		mat[t][x]^=1;
		ll ans=0;
		updateone(1,1,bit[n],x,val,t,0);
		for(int k=0;k<=n;k++)  //区间长度
		{
			ans+=valz[t][k]*valz[t^1][k];
		}
		ans=sum-ans;
		printf("%lld\n",4*ans+1);
	}
	
}

 

你可能感兴趣的:(线段树,思维题)