[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;
}

浙公网安备 33010602011771号