[BZOJ4911][SDOI2017]切树游戏-链分治-快速沃尔什变换

切树游戏

Description

小Q是一个热爱学习的人,他经常去维基百科学习计算机科学。

就在刚才,小Q认真地学习了一系列位运算符,其中按位异或的运算符 对他影响很大。按位异或的运算符是双目运算符。按位异或具有交换律,即 ij=ji

他发现,按位异或可以理解成被运算的数字的二进制位对应位如果相同,则结果的该位置为0,否则为1,例如: 1(01)2(10)=3(11)

他还发现,按位异或可以理解成参与运算的数字的每个二进制位都进行了不进位的加法,例如: 3(11)3(11)=0(00)

现在小Q有一棵 n 个结点的无根树T,结点依次编号为 1 n,其中结点 i 的权值为vi

定义一棵树的价值为它所有点的权值的异或和,一棵树 T 的连通子树就是它的一个连通子图,并且这个图也是一棵树。

小Q想要在这棵树上玩切树游戏,他会不断做以下两种操作:

Change x y 将编号为x的结点的权值修改为 y

Query k 询问有多少棵T的非空连通子树,满足其价值恰好为 k
小Q非常喜(bu)欢(hui)数学,他希望你能快速回答他的问题,你能写个程序帮帮他吗?

Input

第一行包含两个正整数n , m ,分别表示结点的个数以及权值的上限。

第二行包含n个非负整数 v1,v2,,vn ,分别表示每个结点一开始的权值。

接下来 n1 行,每行包含两个正整数 ai,bi ,表示有一条连接 ai bi 的无向树边。

接下来一行包含一个正整数 q ,表示小Q操作的次数。

接下来q行每行依次表示每个操作。

Output

输出若干行,每行一个整数,依次回答每个询问。因为答案可能很大,所以请对1000710007取模输出。

Sample Input

4 4
2 0 1 3
1 2
1 3
1 4
12
Query 0
Query 1
Query 2
Query 3
Change 1 0
Change 2 1
Change 3 3
Change 4 1
Query 0
Query 1
Query 2
Query 3

Sample Output

3
3
2
3
2
4
2
3

Hint

对于 100% 的数据, 1ai,bi,xn 0vi,y,k<m ,修改操作不超过 10000 个。


神题一道,学习到了链分治的新姿势~
然而第一次写会感觉特别难写难调…..
于是本蒟蒻调了一上午+一下午


思路:

首先可以列出朴素的dp方程:
g[i][j][k] 代表当前在 i 节点,处理到了j号儿子 chj ,子树异或和为 k 的方案数。
f[i][j]代表当前在 i 节点,子树异或和为j的方案数。

那么:
g[i][1][k]=f[ch(i)1][k]
g[i][j][k]=g[i][j1][l](f[ch(i)j][kw]+[k==w])
f[i][j]=g[i][|ch(i)|][j]

可以发现第二行的转移是个异或卷积的形式,那么套个FWT变成 O(128) 转移。
此时,如果是静态查询,很明显一发 dsu on tree 就做完了~

然而这是动态的。
所以考虑使用链分治。

什么是链分治?
首先考虑树链剖分,找出重链。
链分治就是,对每条重链上的信息,单独建一棵线段树来维护,从而达到动态修改的效果。
在链分治时,将会直接用单独的一棵线段树维护一根重链的信息,此时每个节点的权值便是它只考虑所有轻边的情况下的dp值。
然后计算某条重链链顶的贡献时,在线段树向上更新合并区间时互相dp,最后根节点的信息便是这条链链顶的结果了。
然后把这个根节点的贡献传到链顶的父亲所在线段树中属于父亲的叶子结点处,并更新父亲所在重链的线段树,以此类推,直到更新完根节点时就完成了!

对于这道题,需要维护四个tag,其中一个是子树的 f 值之和h
可以发现在进行一次FWT后,令 ei [i==0] 的数组的FWT结果,则更新dp的式子写成生成函数后如下:
fi(e)=evijchfj(e)+e0

也就是说,对于每个点,要维护来自轻边的贡献,而这是一个连乘,直接做即可~。
剩下的是合并信息,同样是乘法合并。
于是需要维护区间乘以及每一小段乘积之和(这是 h <script type="math/tex" id="MathJax-Element-12261">h</script>),这些就是线段树基本操作了,具体见代码~
考虑到0的逆元问题,需要手写一个可以在模意义下除以0并乘以0的类(就是记录是否被过多的0乘过)。

#include<iostream>
#include<vector>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;

#define mid ((l+r)>>1)
typedef long long ll;
const int N=30009;
const int M=N*3;
const int K=309;
const int md=10007;
const int inv2=(md+1)>>1;

int n,k,m,l;
int val[N],ans[K],tmp[K];
int to[N<<1],nxt[N<<1],beg[N],tot;
int fa[N],siz[N],son[N],dep[N];
int top[N],stk[N],stop;
int inv[md+3],e[K][K];
int root[N],pos[N],pool;
int h[M][K],lval[M][K],rval[M][K],sum[M][K],ls[M],rs[M],tfa[M];
vector<int> v[N];

struct int0
{
    int a,b;
    int0(int _a=0)
    {
        if(_a)a=_a,b=0;
        else a=1,b=1;
    }

    friend int0 operator * (int0 o,int v)
    {
        if(!v)o.b++;
        else o.a=o.a*v%md;
        return o;
    }

    friend int0 operator / (int0 o,int v)
    {
        if(!v)o.b--;
        else o.a=o.a*inv[v]%md;
        return o;
    }

    inline int get()
    {
        if(b)return 0;
        else return a;
    }
}f[N][K];

inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0' || '9'<ch){if(ch=='-')f=-1;ch=getchar();}
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x*f;
}

inline void add(int u,int v)
{
    to[++tot]=v;
    nxt[tot]=beg[u];
    beg[u]=tot;
}

inline int qpow(int a,int b)
{
    int ret=1;
    while(b)
    {
        if(b&1)
            ret=ret*a%md;
        a=a*a%md;
        b>>=1;
    }
    return ret;
}

inline void FWT(int *a,int n,bool f)
{
    for(int h=2;h<=n;h<<=1)
        for(int j=0;j<n;j+=h)
            for(int k=j;k<j+(h>>1);k++)
            {
                int x=a[k],y=a[k+(h>>1)];
                a[k]=(x+y)%md;
                a[k+(h>>1)]=(x-y+md)%md;
                if(f)
                {
                    a[k]=a[k]*inv2%md;
                    a[k+(h>>1)]=a[k+(h>>1)]*inv2%md;
                }
            }
}

inline void init()
{
    for(m=1;m<k;m<<=1);k=m;
    for(int i=0;i<k;i++)
    {
        e[i][i]=1;
        FWT(e[i],k,0);
    }

    inv[1]=1;
    for(int i=2;i<md;i++)
        inv[i]=qpow(i,md-2);

    for(int i=1;i<=n;i++)
        for(int j=0;j<k;j++)
            f[i][j]=int0(e[val[i]][j]);
}

inline void dfs(int u)
{
    siz[u]=1;son[u]=0;
    for(int i=beg[u],v;i;i=nxt[i])
        if((v=to[i])!=fa[u])
        {
            fa[v]=u;
            dep[v]=dep[u]+1;
            dfs(v);
            siz[u]+=siz[v];
            if(!son[u] || siz[son[u]]<siz[v])
                son[u]=v;
        }
}

inline void dfs2(int u)
{
    v[top[u]].push_back(u);
    if(top[u]==u)stk[++stop]=u;
    if(son[u])
    {
        top[son[u]]=top[u];
        dfs2(son[u]);
        for(int i=beg[u],v;i;i=nxt[i])
            if((v=to[i])!=fa[u] && v!=son[u])
                dfs2(top[v]=v);
    }
}


inline bool cmp(int a,int b)
{
    return dep[a]>dep[b];
}

inline void update(int x)
{
    for(int i=0;i<k;i++)
    {
        h[x][i]=(h[ls[x]][i]+h[rs[x]][i]+rval[ls[x]][i]*lval[rs[x]][i])%md;
        lval[x][i]=(lval[ls[x]][i]+sum[ls[x]][i]*lval[rs[x]][i])%md;
        rval[x][i]=(rval[rs[x]][i]+sum[rs[x]][i]*rval[ls[x]][i])%md;
        sum[x][i]=sum[ls[x]][i]*sum[rs[x]][i]%md;
    }
}

inline int build(int l,int r,int x)
{
    int now=++pool;
    if(l==r)
    {
        for(int i=0;i<k;i++)
            h[now][i]=lval[now][i]=rval[now][i]=sum[now][i]=f[v[x][l-1]][i].get();
        pos[v[x][l-1]]=now;
        return now;
    }
    tfa[ls[now]=build(l,mid,x)]=now;
    tfa[rs[now]=build(mid+1,r,x)]=now;
    update(now);
    return now;
}

inline void change(int u)
{
    int x=pos[u],tp=top[u];

    if(fa[tp])
        for(int i=0;i<k;i++)
            f[fa[tp]][i]=f[fa[tp]][i]/((lval[root[tp]][i]+e[0][i])%md);
    for(int i=0;i<k;i++)
        ans[i]=(ans[i]-h[root[tp]][i]+md)%md;

    for(int i=0;i<k;i++)
        sum[x][i]=lval[x][i]=rval[x][i]=h[x][i]=f[u][i].get();
    for(x=tfa[x];x;x=tfa[x])
        update(x);

    if(fa[tp])
        for(int i=0;i<k;i++)
            f[fa[tp]][i]=f[fa[tp]][i]*((lval[root[tp]][i]+e[0][i])%md);
    for(int i=0;i<k;i++)
        ans[i]=(ans[i]+h[root[tp]][i])%md;
}

int main()
{
    n=read();k=read();
    for(int i=1;i<=n;i++)
        val[i]=read();
    for(int i=1,x,y;i<n;i++)
    {
        x=read();y=read();
        add(x,y);add(y,x);
    }
    init();

    dfs(1);
    dfs2(top[1]=1);
    sort(stk+1,stk+stop+1,cmp);

    for(int i=1;i<=stop;i++)
    {
        int u=stk[i];
        root[u]=build(1,v[u].size(),u);     
        for(int j=0;j<k;j++)
            ans[j]=(ans[j]+h[root[u]][j])%md;
        if(fa[u])
            for(int j=0;j<k;j++)
                f[fa[u]][j]=f[fa[u]][j]*((lval[root[u]][j]+e[0][j])%md);    
    }

    int q=read(),a,b;
    char s[100];
    while(q--)
    {
        scanf("%s",s+1);
        if(s[1]=='C')
        {
            a=read();b=read();
            for(int i=0;i<k;i++)
                f[a][i]=f[a][i]/e[val[a]][i];
            val[a]=b;
            for(int i=0;i<k;i++)
                f[a][i]=f[a][i]*e[val[a]][i];
            for(;a;a=fa[top[a]])
                change(a);
        }
        else
        {
            a=read();
            for(int i=0;i<k;i++)
                tmp[i]=ans[i];
            FWT(tmp,k,1);
            printf("%d\n",tmp[a]);
        }
    }

    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值