[SDOI2017]切树游戏

题面

这道题从昨天下午写到今天下午

我好菜啊

感到绝望

算法一

考虑朴素dp

\(dp[x][i]\)表示以\(x\)为根的子树中权值\(i\)的子树个数

考虑加入子节点\(y\)的转移
\[ dp[x][k]+=\sum_{i~xor~j=k}{dp[x][i]*dp[y][j]} \]
答案即为
\[ \sum_{i=1}^{n}{dp[i][k]} \]
复杂度\(O(QNM^2)\)

算法二

注意到可以使用FWT优化

\(DP[x]\)表示\(FWT(dp[x])\)
\[ DP[x]+=DP[x]*DP[y] \]

\[ DP[x]*=DP[y]+1 \]
注意因为已经\(FWT\)过了

所以这里的乘法是按位相乘

所以那个\(1\)指的是一个全\(1\)的数组

定义Poly类记录\(128\)个位置的\(dp\)

FWT后计算

最后IFWT回来

复杂度\(O(QNMlogM)\)

算法三

发现这是个动态DP

\(dfn[x]\)表示\(x\)\(dfs\)

\(F[dfn[x]]\)表示\(x\)DP


\[ F[dfn[x]]=\prod_{y~is ~son~of~x}{(F[dfn[y]]+1)} \]
\(f[dfn[x]]\)表示\(x\)轻儿子的DP


\[ f[dfn[x]]=\prod_{y~is~light~son~of~x}{(F[dfn[y]]+1)} \]
\(G[dfn[x]]\)表示\(x\)子树内的答案


\[ G[dfn[x]]=\sum_{y~is~son~of~x}G[dfn[y]] \]
\(g[dfn[x]]\)表示\(x\)轻子树内的答案


\[ g[dfn[x]]=\sum_{y~is~light~son~of~x}{G[dfn[y]]} \]
注意以上数组经过FWT后计算

\(y​\)\(x​\)的重儿子

有转移
\[ F[dfn[x]]=f[dfn[x]]*(F[dfn[y]]+1)\\ G[dfn[x]]=g[dfn[x]]+F[dfn[x]]+G[dfn[y]] \]
\(dfn[x]=i\)那么\(dfn[y]=i+1\)


\[ F[i]=f[i]*F[i+1]+f[i]\\ G[i]=g[i]+f[i]*F[i+1]+f[i]+G[i+1]\\ \]
写成矩阵就是
\[ \left[ \begin{matrix} F_{i+1}&G_{i+1}&1 \end{matrix} \right] * \left[ \begin{matrix} f_i&f_i&0\\ 0&1&0\\ f_i&f_i+g_i&1 \end{matrix} \right] =\left[ \begin{matrix} F_{i}&G_{i}&1 \end{matrix} \right] \]
考虑合并矩阵
\[ \left[ \begin{matrix} a&b&0\\ 0&1&0\\ c&d&1 \end{matrix} \right]* \left[ \begin{matrix} A&B&0\\ 0&1&0\\ C&D&1 \end{matrix} \right] = \left[ \begin{matrix} aA&aB+b&0\\ 0&1&0\\ cA+C&cB+d+D&1 \end{matrix} \right] \]
发现只需记录\(4\)个位置的值即可

定义Info类记录矩阵

动态DP

复杂度\(QMlog^2N\)

可以通过

考虑细节

细节一

修改\(x\)的时候\(f[dfn[fa[x]]]\)需要除去原来的贡献

因为是在\(\mod 10007\)意义下求解

所以需要求出逆元

然而\(0\)(\(10007\)的倍数)没有逆元

似乎有一种记录\(0\)个数的做法

但是更自然的想法是对每个节点维护一颗线段树专门维护\(f​\)数组

维护像这种不可减的信息都是这样用数据结构维护的

比如最值可以用multiset维护

所以直接修改线段树即可

并不会成为复杂度瓶颈

但是因为每个点都要开

所以必须动态开点

细节二

最后统计答案乘上初始矩阵\(\left[\begin{matrix}0&0&1 \end{matrix} \right]​\)

直接取出答案就行了

不需要实现矩阵乘法

这是套路
\[ \left[\begin{matrix}0&0&1 \end{matrix} \right]* \left[ \begin{matrix} a&b&0\\ 0&1&0\\ c&d&1 \end{matrix} \right] =\left[\begin{matrix}c&d&1 \end{matrix} \right] \]
细节三

因为是从下往上的dp

也就是线段树是反着维护矩阵的

这是套路

#include<bits/stdc++.h>

using namespace std;

#define gc c=getchar()
#define r(x) read(x)
#define ll long long

template<typename T>
inline void read(T&x){
    x=0;T k=1;char gc;
    while(!isdigit(c)){if(c=='-')k=-1;gc;}
    while(isdigit(c)){x=x*10+c-'0';gc;}x*=k;
}

const int N=3e4+7;
const int M=128|7;
const int p=10007;

inline int add(int a,int b){
    return (a+=b)>=p?a-p:a;
}

inline int sub(int a,int b){
    return (a-=b)<0?a+p:a;
}

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

int n,m;
int Inv;

struct Poly{
    
    int A[M];
    
    inline void FWT(){
        for(int i=1;i<m;i<<=1){
            for(int j=0;j<m;j+=(i<<1)){
                for(int k=0;k<i;++k){
                    int u=A[j+k],v=A[i+j+k];
                    A[j+k]=add(u,v),A[i+j+k]=sub(u,v);
                }
            }
        }
    }
    
    inline void IFWT(){
        FWT();
        for(int i=0;i<m;++i){(A[i]*=Inv)%=p;}
    }
    
    inline Poly(){}
    
    inline Poly(int x){
        memset(A,0,m<<2);
        A[x]=1;
        FWT();
    }
    
    inline int& operator [] (const int &x){return A[x];}
    
    inline const int& operator [] (const int &x)const {return A[x];}
    
    inline void operator =(const Poly &T){
        memcpy(A,T.A,m<<2);
    }
    
    inline void operator *= (const Poly &T){
        for(int i=0;i<m;++i)(A[i]*=T[i])%=p;
    }
    
    inline Poly operator * (const Poly &T)const {
        Poly ret;
        for(int i=0;i<m;++i)ret[i]=A[i]*T[i]%p;
        return ret;
    }
    
    inline void operator += (const Poly &T){
        for(int i=0;i<m;++i)A[i]=add(A[i],T[i]);
    }
    
    inline Poly operator +(const Poly &T)const {
        Poly ret;
        for(int i=0;i<m;++i)ret[i]=add(A[i],T[i]);
        return ret;
    }
    
    inline void operator -= (const Poly &T){
        for(int i=0;i<m;++i)A[i]=sub(A[i],T[i]);
    }
    
    inline Poly operator -(const Poly &T)const {
        Poly ret;
        for(int i=0;i<m;++i)ret[i]=sub(A[i],T[i]);
        return ret;
    }
}one,F[N],G[N],g[N];

int root[N];
int cnt[N];

namespace Data{
    int tot;
    Poly tr[N*10];
    int ls[N*10];
    int rs[N*10];
    int siz[N*10];
    #define ls ls[rt]
    #define rs rs[rt]
    
    inline void update(int rt){
        tr[rt]=tr[ls]*tr[rs];
    }
    
    void insert(int &rt,int l,int r,int x,const Poly &v){
        if(!rt)rt=++tot;
        ++siz[rt];
        if(l==r)return void(tr[rt]=v);
        int mid=(l+r)>>1;
        if(x<=mid)insert(ls,l,mid,x,v);
        else insert(rs,mid+1,r,x,v);
        if(siz[rt]>r-l)update(rt);
    }
    #undef ls
    #undef rs
};

int a[N];

vector<int>E[N];

int fa[N],siz[N],dep[N],son[N];

void dfs1(int x,int f){
    fa[x]=f;
    siz[x]=1;
    dep[x]=dep[f]+1;
    for(int i=0;i<E[x].size();++i){
        int v=E[x][i];
        if(v==f)continue;
        dfs1(v,x);
        siz[x]+=siz[v];
        if(siz[son[x]]<siz[v])son[x]=v;
    }
}

int dfs_clock;
int top[N],bot[N],dfn[N],ptn[N];

void dfs2(int x,int t){
    top[x]=t;
    dfn[x]=++dfs_clock;
    ptn[dfs_clock]=x;
    if(!son[x])return void(bot[x]=x);
    dfs2(son[x],t);
    bot[x]=bot[son[x]];
    for(int i=0;i<E[x].size();++i){
        int v=E[x][i];
        if(v==fa[x]||v==son[x])continue;
        dfs2(v,v);
    }
}

int pos[N];

void dfs3(int x){
    F[x]=Poly(a[x]);
    for(int i=0;i<E[x].size();++i){
        int v=E[x][i];
        if(v==fa[x])continue;
        dfs3(v);
        F[x]*=F[v]+one;
        G[x]+=G[v];
    }
    G[x]+=F[x];
    for(int i=0;i<E[x].size();++i){
        int v=E[x][i];
        if(v==fa[x]||v==son[x])continue;
        pos[v]=++cnt[x];
    }
    cnt[x]++;
    Data::insert(root[x],1,cnt[x],cnt[x],Poly(a[x]));
    for(int i=0;i<E[x].size();++i){
        int v=E[x][i];
        if(v==fa[x]||v==son[x])continue;
        g[x]+=G[v];
        Data::insert(root[x],1,cnt[x],pos[v],F[v]+one);
    }
}

struct Info{
    Poly a,b,c,d;
    
    inline Info(){};
    inline Info(const Poly &f,const Poly &g):a(f),b(f),c(f),d(f+g){};
    inline Info(const Poly &A,const Poly &B,const Poly &C,const Poly &D):a(A),b(B),c(C),d(D){}
    
    inline Info operator + (const Info &x){
        return Info(a*x.a,a*x.b+b,c*x.a+x.c,c*x.b+d+x.d);
    }
    
    inline Poly f(){
        return c;
    }
    
    inline Poly g(){
        return d;
    }
    
}tr[N<<2];

#define ls (rt<<1)
#define rs (rt<<1|1)

inline void update(int rt){
    tr[rt]=tr[rs]+tr[ls];
}

void build(int rt,int l,int r){
    if(l==r){
        int x=ptn[l];
        tr[rt]=Info(Data::tr[root[x]],g[x]);
        return ;
    }
    int mid=(l+r)>>1;
    build(ls,l,mid);
    build(rs,mid+1,r);
    update(rt);
}

void modify(int rt,int l,int r,int x,const Info &v){
    if(l==r){
        tr[rt]=v;
        return ;
    }
    int mid=(l+r)>>1;
    if(x<=mid)modify(ls,l,mid,x,v);
    else modify(rs,mid+1,r,x,v);
    update(rt);
}

Info query(int rt,int l,int r,int x,int y){
    if(x<=l&&r<=y)return tr[rt];
    int mid=(l+r)>>1;
    if(y<=mid)return query(ls,l,mid,x,y);
    if(x>mid)return query(rs,mid+1,r,x,y);
    return query(rs,mid+1,r,x,y)+query(ls,l,mid,x,y);
}

inline void Change(int x,int y){
    a[x]=y;
    Data::insert(root[x],1,cnt[x],cnt[x],Poly(a[x]));
    while(x){
        modify(1,1,n,dfn[x],Info(Data::tr[root[x]],g[x]));
        x=top[x];
        Info tmp=query(1,1,n,dfn[x],dfn[bot[x]]);
        if(fa[x])Data::insert(root[fa[x]],1,cnt[fa[x]],pos[x],Poly(tmp.f()+one));
        if(fa[x])g[fa[x]]-=G[x];
        G[x]=tmp.g();
        if(fa[x])g[fa[x]]+=G[x];
        x=fa[x];
    }
}

inline int Query(int x){
    Poly t=query(1,1,n,dfn[1],dfn[bot[1]]).g();
    t.IFWT();
    return t[x];
}

inline void init(){
    r(n),r(m);Inv=qpow(m,p-2);
    for(int i=0;i<m;++i)one[i]=1;
    for(int i=1;i<=n;++i){
        r(a[i]);
    }
    for(int i=1;i<n;++i){
        int u,v;r(u),r(v);
        E[u].push_back(v);
        E[v].push_back(u);
    }
    dfs1(1,0);
    dfs2(1,1);
    dfs3(1);
    build(1,1,n);
}

int main(){
//  freopen(".in","r",stdin);
//  freopen(".out","w",stdout);
    init();
    int q;r(q);
    while(q--){
        char s[10];
        scanf("%s",s);
        if(s[0]=='C'){
            int x,y;r(x),r(y);
            Change(x,y);
        }
        else {
            int k;r(k);
            printf("%d\n",Query(k));
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/yicongli/p/10438052.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值