【洛谷】T46495 子异和 -拆位找规律&线段树

传送门: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两部分,分别处理后加起来:

  • 若当前数 a i a_i ai二进制第 x x x位为 1 1 1: t [ i ] [ x ] = t [ i − 1 ] [ x ] + ( 2 i − 1 − 1 − t [ i − 1 ] [ x ] ) + 1 = 2 i − 1 t[i][x]=t[i-1][x]+(2^{i-1}-1-t[i-1][x])+1=2^{i-1} t[i][x]=t[i1][x]+(2i11t[i1][x])+1=2i1
  • 若当前数 a i a_i ai二进制第 x x x位为 0 0 0: t [ i ] [ x ] = t [ i − 1 ] [ x ] × 2 t[i][x]=t[i-1][x]\times 2 t[i][x]=t[i1][x]×2

显然能够得到结论:集合 S = a 1 , a 2 , . . , a ∣ S ∣ S={a_1,a_2,..,a_{|S|}} S=a1,a2,..,aS的子异和 = ( a 1 ∣ a 2 ∣ . . . ∣ a ∣ S ∣ ) × 2 ∣ S ∣ − 1 =(a_1|a_2|...|a_{|S|})\times 2^{|S|-1} =(a1a2...aS)×2S1

对于询问, 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]]

你可能感兴趣的:(妙,线段树)