[GDKOI2016]魔卡少女 题解

[GDKOI2016]魔卡少女

来源: GDKOI Day1 T1

5月4日当天的一场考试的 T1

一道做法巧妙的线段树好题,维护的信息很新颖,很有启发性

算法标签

二进制拆位的线段树

题目难度

\(2100\)

题面

给定一个长度为 \(n\) 的序列 \(a\) ,有 \(m\) 次操作,操作分为两种类型。

第一种:把 \(x\) 位置的数改成 \(y\)
第二种:询问区间 \([l,r]\) 所有子区间的异或和之和

\(1\le n,m\le1e5\)\(1\le a_i\le1e3\)

问题分析

首先考虑直接维护异或和信息,发现对于两个区间,不管记录什么信息,都不能求出合并出来的大区间的信息。

之前的线段树维护区间和,区间最值等信息时,容易维护的原因在于,两个子区间的和加起来就是合并成的大区间的和,子区间最大值比较一下就是新区间最大值,这些信息都具有可合并性。

发现导致此题难以直接维护的根本在于异或没有分配律,区间异或和不具有合并性(一般分析出某信息不具有合并性,就要从题目的特殊之处入手分析了),又发现序列的值域很小,只有 \(1e3\) ,而对于位运算来说,二进制下的操作无疑是最方便维护的,于是想到二进制拆位,把序列中的数字看作二进制,最多 \(10\) 位,于是建立 \(10\) 颗线段树来维护整个序列每一个二进制位的信息,每颗线段树维护的就是一个 \(01\) 序列。

在现在的 \(01\) 序列状态下,若第 \(i\) 颗线段树维护的 \(01\) 序列所有子区间异或和之和为 \(s_i\) (也即子区间异或和为 \(1\) 的数量),则说明在原序列中有 \(s_i\) 个子区间满足其异或和上该位为 \(1\) ,由于求的是异或和之和,那么该位就被贡献了 \(s_i\) 次,也即 \(s_i\)\(2^{i-1}\)

现在考虑,维护什么信息,才能推出一个区间的所有子区间异或和,注意到对于一个 \(01\) 区间,当区间中 \(1\) 的个数为奇数时,结果为 \(1\) ,其他情况均为 \(0\) ,维护以下信息。

\(sum\) :该区间内所有位的异或和。

\(ans\) :该区间内所有子区间异或和为 \(1\) 的数量。

\(l_0\) :该区间内以区间左端点为起点的所有区间中,异或和为 \(0\) 的区间数量。

\(l_1\) :该区间内以区间左端点为起点的所有区间中,异或和为 \(1\) 的区间数量。

\(r_0\) :该区间内以区间右端点为起点的所有区间中,异或和为 \(0\) 的区间数量。

\(r_1\) :该区间内以区间右端点为起点的所有区间中,异或和为 \(1\) 的区间数量。

这些信息如何更新?

对于 \(sum\) ,只需左区间与右区间的 \(sum\) 异或即可。

对于 \(l_0\) ,首先,合并成的新区间的区间左端点还是左区间的左端点,所以在左区间中,所有以左区间左端点为起点,异或和为 \(0\) 的子区间,都会成为合并后区间的答案的一部分,然后所以终点在右区间中的区间,一定包含了完整的左区间,所以若左区间得到整个的异或和( \(a_l\) ^ \(...\) ^ \(a_{mid}\)),那么在右区间中的部分只要和左区间异或和相同,那么合并后新区间的这一段就是 \(0\) (也就是说 \(x\) ^ \(x=0\) )。

另外三个同理讨论即可。

对于 \(ans\) ,首先是左区间的 \(ans\) ,与右区间的 \(ans\) ,显然会作为新区间的 \(ans\) 的一部分(看一眼定义就明白了) ,之前提到,异或和为 \(1\) 的子区间才会贡献 \(ans\) ,于是 \(ans\) 可求,具体可以看代码。

实现细节

有一些比较好写的写法,可以有效减少代码量。

线段树使用结构体封装一下,然后线段树的数组也定义成结构体(里面存了刚刚提到的 \(6\) 个重要信息)。

因为两个子区间合并时是很麻烦的,所以建议把合并写成一个返回值为结构体的 \(merge\) 函数。

代码实现

#define N 100005
#define p 100000007
#define ll long long
using namespace std;
int n,m,l,r,a[N]; 
ll ans,s;
char op[5];
struct Point
{
	int l[2],r[2];
	ll sum,ans;
	Point(){l[0]=l[1]=r[0]=r[1]=sum=ans=0;}
};
struct segment_tree
{
	Point tree[N<<2];
	Point merge(Point a,Point b)
	{
		Point c;
        c.sum=a.sum^b.sum;
        c.l[0]=a.l[0]+b.l[a.sum];
        c.l[1]=a.l[1]+b.l[a.sum^1];
        c.r[0]=a.r[b.sum]+b.r[0];
        c.r[1]=a.r[b.sum^1]+b.r[1];
        c.ans=a.ans+b.ans+1ll*a.r[0]*b.l[1]+1ll*a.r[1]*b.l[0];
        return c;
	}
	void pushup(int node){tree[node]=merge(tree[node<<1],tree[node<<1|1]);}
	void update(int l,int r,int node,int x,int v)
	{
		if(r<x||l>x) return;
		if(l==r&&l==x)
		{
			tree[node].l[1]=tree[node].r[1]=tree[node].sum=tree[node].ans=v;
			tree[node].l[0]=tree[node].r[0]=(v^1);
			return;
		}
		int mid=(l+r)>>1;
		update(l,mid,node<<1,x,v);
		update(mid+1,r,node<<1|1,x,v);
		pushup(node);
	}
	Point query(int l,int r,int node,int x,int y)
	{
		if(r<x||l>y) 
		{
			Point emp;
			return emp;
		}
		if(x<=l&&r<=y) return tree[node];
		int mid=(l+r)>>1;
		Point a=query(l,mid,node<<1,x,y),b=query(mid+1,r,node<<1|1,x,y);
        return merge(a,b);     
	}
}rt[10];
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
	return x*f;
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
    	a[i]=read();
    	for(int j=0;j<=9;j++) rt[j].update(1,n,1,i,(a[i]>>j)&1);
	}
	m=read();
	for(int T=1;T<=m;T++)
	{
		scanf("%s",op);l=read();r=read();
		if(op[0]=='M')
		{
			for(int j=0;j<=9;j++) rt[j].update(1,n,1,l,(r>>j)&1);	
		}
		else
		{
			s=0;
			for(int j=0;j<=9;j++)
			{
                Point tmp=rt[j].query(1,n,1,l,r);
                s+=(tmp.ans<<j);
            }
            printf("%lld\n",s%p);
		}
	}
    return 0; 
}
posted @ 2022-05-04 21:35  Constant1227  阅读(148)  评论(0)    收藏  举报