传送门:luoguT46495 子异和
这题的性质和维护都很妙啊。
一个数集的子异和为其所有非空子集的集合异或和之和。
考虑如何 O ( n ) O(n) O(n)回答单次询问:
拆位后用桶 t [ i ] [ x ] t[i][x] t[i][x]表示第 x x x位在前 i i i个数组成的所有非空子集中出现的总次数。
可以把前 i i i个数组成的所有非空子集分成含有 a i a_i ai和不含有 a i a_i ai两部分,分别处理后加起来:
显然能够得到结论:集合 S = a 1 , a 2 , . . , a ∣ S ∣ S={a_1,a_2,..,a_{|S|}} S=a1,a2,..,a∣S∣的子异和 = ( a 1 ∣ a 2 ∣ . . . ∣ a ∣ S ∣ ) × 2 ∣ S ∣ − 1 =(a_1|a_2|...|a_{|S|})\times 2^{|S|-1} =(a1∣a2∣...∣a∣S∣)×2∣S∣−1
对于询问, d f s dfs dfs序+线段树维护区间异或和即可。
为了维护每次修改后的异或和,还需要维护区间与,再深入发掘一下性质:
对于一个区间的数异或上一个 c c c时,观察到:
从 0 0 0变成 1 1 1的位必然满足 c c c的二进制对应位为 1 1 1,且区间或值对应位为 0 0 0。
从 1 1 1变成 0 0 0的位必然满足 c c c的二进制对应位为 1 1 1,且区间与值对应位为 1 1 1。
这样维护就好了。
#include
#define mid ((l+r)>>1)
#define lc k<<1
#define rc k<<1|1
using namespace std;
typedef long long ll;
const int N=2e5+100,mod=1e9+7;
const int maxn=(1LL<<31)-1;
int n,m,val[N],bin[N];
int head[N],to[N<<1],nxt[N<<1],tot;
int df[N],top[N],sz[N],son[N],f[N],dep[N],dfn;
int qx[N<<2],qy[N<<2],lzy[N<<2];
inline void lk(int u,int v)
{to[++tot]=v;nxt[tot]=head[u];head[u]=tot;}
inline int ad(int x,int y){x+=y;return x>=mod?x-mod:x;}
char cp,SS[100];
template
inline void rd(yyy &x)
{
cp=getchar();x=0;
for(;!isdigit(cp);cp=getchar());
for(;isdigit(cp);cp=getchar()) x=(x<<3)+(x<<1)+(cp^48);
}
inline void ot(int x)
{
int re=0;
for(;(!re)||(x);x/=10) SS[++re]='0'+x%10;
for(;re;--re) putchar(SS[re]);
putchar('\n');
}
void dfs(int x)
{
sz[x]=1;
for(int j,i=head[x];i;i=nxt[i]){
j=to[i];if(j==f[x]) continue;
f[j]=x;dep[j]=dep[x]+1;dfs(j);
sz[x]+=sz[j];if(sz[son[x]]mid) return gt(rc,mid+1,r,L,R);
qx[k]=qx[lc]|qx[rc];
qy[k]=qy[lc]&qy[rc];
return (gt(lc,l,mid,L,R)|gt(rc,mid+1,r,L,R));
}
void modify(int k,int l,int r,int L,int R,int v)
{
if(L<=l && r<=R){trs(k,v);return;}
pushdown(k);
if(L<=mid) modify(lc,l,mid,L,R,v);
if(R>mid) modify(rc,mid+1,r,L,R,v);
qx[k]=qx[lc]|qx[rc];
qy[k]=qy[lc]&qy[rc];
}
inline int ask(int x,int y)
{
int re=0,cot=-1;
for(;top[x]!=top[y];x=f[top[x]]){
if(dep[top[x]]