[USACO14OPEN] Code Breaking G 题解

[USACO14OPEN] Code Breaking G

给定一棵 \(n\) 个节点的树,每个点可以填 \(0\) 到 \(9\) 的一个数字。给定了 \(m\) 条限制,要求节点 \(x\) 及以上 \(4\) 个祖先节点不能是某种填法,问不合法的方案数。\(n,m\le 5\times 10^4\)。

首先要想到容斥做法,枚举哪些限制被钦定不满足,当枚举的限制可以同时不满足时,此时至少不满足这些限制的方案数 \(f(S)\) 为 \(10^{cnt}\),其中 \(10^{cnt}\) 表示未被限制的节点的方案数。设 \(g(S)\) 为恰好不满足这些限制的方案数,由子集反演得,\(g(0)=\sum_{0\subseteq S} (-1)^{|S|}f(S)\),则答案就是 \(10^n -g(0)\),时间复杂度 \(O(m2^m)\),无法接受。

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=2;i<=n;i++){
        cin>>fa[i];
        fa[i]++;
        g[fa[i]].push_back(i);
    }
    for(int i=1;i<=m;i++){
        cin>>d[i]>>s[i];
        d[i]++;int x=d[i];
        int k=0;
        for(int j=4;j>=0;j--){
            k*=10;
            k+=s[i][j]-'0';
        }
        val[x].push_back(k);
    }
    for(int t=0;t<(1<<m);t++){
        cnt[t]=n;
        for(int i=1;i<=n;i++)v[i]=-1;
        for(int i=1;i<=m;i++){
            if(!((t>>(i-1))&1))continue;
            int x=d[i];
            for(int j=0;j<=4;j++){
                if(v[x]!=-1&&v[x]!=s[i][j]-'0'){
                    ilg[t]=1;
                    break;
                }
                if(v[x]==-1)cnt[t]--;
                v[x]=s[i][j]-'0';
                x=fa[x];
            }
            if(ilg[t])break;
        }
    }
    for(int s=0;s<(1<<m);s++){
        if(ilg[s])continue;
        f[s]=qpow(10,cnt[s]);
    }
    int ans=qpow(10,n);
    for(int t=0;t<(1<<m);t++){
        if(popcount(t)&1)add(h0,(mod-f[t])%mod);
        else add(h0,f[t]);
    }
    cout<<(ans-h0+mod)%mod;
    return 0;
}

回忆一下我们做过的主旋律那题,我们可以把容斥系数带进 DP 里计算,设 \(f_{x,S}\) 表示 \(x\) 节点根据已经钦定的限制,其祖先要填的数的数列是 \(S\) 时的带容斥系数的方案数。注意,其中 \(f_{x,S}\) 的 \(S\) 是含有 \(x\) 自己填什么的,而其儿子 \(v\) 的 \(f_{v,T}\) 的 \(T\) 不包含 \(v\) 自己填什么,也是从 \(x\) 开始。

显然,如果儿子的限制和自己的限制对不上,则不能转移,那就是要求 \(S\) 是 \(T\) 的前缀,或 \(T\) 是 \(S\) 的前缀(因为儿子的限制可能受的比 \(x\) 还多),那就有转移 \(f_{x,S} \leftarrow f_{x,S}\times \sum_{t}f_{v,t} + \sum_{t} f_{x,t} \times f_{v,S}-f_{u,S}\times f_{v,S}\),其中 \(t\) 是 \(S\) 的前缀,减去的是重复算的。带了容斥系数的话,\((-1)^{|S|} \times (-1)^{|t|} = (-1)^{|S|+|t|}\),也是符合我们的状态定义的。

然而暴力枚举所有受限状态也是 \(O(nm)\) 的,不能通过。

回忆一下我们做过的命运那题,在有值的状态不多,而且要求前缀和的转移,我们考虑整体 DP,当然此处不是线段树合并,而是 trie 合并。

一个 trie 树上的节点,恰好可以对应一种受限制的串,所以是完美的选择。特别的,一个点的 \(rt_x\) 节点的 DP 值表示,当前不受任何限制时,含容斥系数的方案数之和。合并两个点的 trie 是比较套路的,注意我们在合并完所有儿子后,要把自己选什么这一层删掉,也就是 \(rt_x\) 下面第一层,方法就是直接取出第一层所有节点,这些点之间做子树合并(只是加法),把这一层的合并节点作为新的 \(rt_x\),当然还要加上原 \(f_{rt_x} \times 10\)。

时间复杂度 \(O(n+5m)\)。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define _int __int128
#define ull unsigned long long
#define pii pair<int,int>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
int n,m;
const int N = 5e4+10,mod=1234567;
int fa[N];
int qpow(int a,int b){
    int res=1;
    while(b){
        if(b&1)res=(res*a)%mod;
        a=(a*a)%mod;
        b>>=1;
    }
    return res;
}
inline void add(int &x,int y){
    x=(x+y>=mod)?x+y-mod:x+y;
}
int ans;
vector<string>val[N];
string s[N];
vector<int>g[N];
int f[15*N],tr[10*N][13],tot,tag[15*N],rt[N];
void newd(int &u){
    if(u)return;
    u=++tot;
    tag[u]=1;
}
void pushdown(int p){
    if(tag[p]!=1){
        for(int i=0;i<10;i++){
            f[tr[p][i]]=(f[tr[p][i]]*tag[p])%mod;
            tag[tr[p][i]]=(tag[tr[p][i]]*tag[p])%mod;
        }
        tag[p]=1;
    }
}
void ins(int p,string s){
    for(auto c:s){
        newd(tr[p][c-'0']);
        p=tr[p][c-'0'];
    }
    f[p]=mod-1;
}
int merge(int u,int v,int s1,int s2){
    if(!u&&!v)return 0;
    if(!u){
        f[v]=(f[v]*s1)%mod;
        tag[v]=(tag[v]*s1)%mod;
        return v;
    }
    if(!v){
        f[u]=(f[u]*s2)%mod;
        tag[u]=(tag[u]*s2)%mod;
        return u;
    }
    pushdown(u);pushdown(v);
    add(s1,f[u]);add(s2,f[v]);
    f[u]=(f[u]*s2%mod+f[v]*s1%mod-f[u]*f[v]%mod+mod)%mod;
    for(int i=0;i<10;i++){
        tr[u][i]=merge(tr[u][i],tr[v][i],s1,s2);
    }
    return u;
}
int upd(int u,int v){
    if(!u||!v)return u+v;
    pushdown(u);pushdown(v);
    f[u]=(f[u]+f[v])%mod;
    for(int i=0;i<10;i++){
        tr[u][i]=upd(tr[u][i],tr[v][i]);
    }
    return u;
}
void dfs(int x){
    newd(rt[x]);
    for(auto s:val[x]){
        ins(rt[x],s);
    }
    f[rt[x]]=1;
    for(auto v:g[x]){
        dfs(v);
        rt[x]=merge(rt[x],rt[v],0,0);
    }
    int nrt=0;
    for(int i=0;i<10;i++){
        nrt=upd(nrt,tr[rt[x]][i]);
    }
    newd(nrt);
    add(f[nrt],f[rt[x]]*10%mod);
    rt[x]=nrt;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=2;i<=n;i++){
        cin>>fa[i];
        fa[i]++;
        g[fa[i]].push_back(i);
    }
    for(int i=1,x;i<=m;i++){
        cin>>x>>s[i];
        x++;
        val[x].push_back(s[i]);
    }
    dfs(1);
    cout<<(qpow(10,n)-f[rt[1]]+mod)%mod;
    return 0;
}
posted @ 2026-10-16 09:16  farenara_sayowell  阅读(4)  评论(0)    收藏  举报