[ZJOI2016] 小星星 题解

[ZJOI2016] 小星星

第一眼不知从何下手。原来有暴力 DP,设 \(f_{x,i,s}\) 表示 \(x\) 对应的是原编号 \(i\),\(x\) 的子树内所占用的编号位集合是 \(s\) 的方案数。转移是类似树上背包的 \(f_{x,i,s} \leftarrow \sum_{t|p=s,t \land p=0} f_{x,i,t} \times f_{v,j,p}\),且原图上 \(i,j\) 之间有连边。枚举子集做的话,时间复杂度是 \(O(n^33^n)\),可得 30 分。

注意到,\(t \land p=0\) 的限制是不重要的,因为只有和当前考虑的子树 \(siz\) 相等的 \(\mathrm{popcount(s)}\) 才会对答案产生影响,虽然代码里并没有显式地保证这一点,因为询问的是全集的方案数,所以最终我们的计算相当于容斥掉了所有不合法的方案。

那现在就是纯粹的 \(\mathrm{or}\) 卷积,用 FMT 高维前缀和令 \(F_{x,i,s} = \sum_{t \subseteq s} f_{x,i,t}\),则 \(F_{x,i,s} \leftarrow \sum F_{x,i,s}\times F_{v,j,s}\)。最后计算答案的时候逆变换回来即可。和容斥做法应该是本质相同的。

时间复杂度 \(O(n^32^n)\)。

#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 = (1<<17);
vector<int>g[N];
int arv[19];
int f[18][18][N],siz[18],tmp[18][N],a[N],b[N],c[N];
void FWT_or(int *a,int op){
    for(int i=0;i<n;i++){
        for(int s=0;s<1<<n;s++){
            if((s>>i)&1){
                a[s]=(a[s]+op*a[s^(1<<i)]);
            }
        }
    }
}
void dp(int x,int fa){
    for(int i=0;i<n;i++){
        for(int s=0;s<(1<<n);s++){
            if((s>>i)&1)f[x][i][s]=1;
        }
    }
    for(auto v:g[x]){
        if(v==fa)continue;
        dp(v,x);
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(!((arv[i+1]>>j)&1))continue;
                for(int s=0;s<(1<<n);s++){
                    tmp[i][s]+=f[x][i][s]*f[v][j][s];
                }
            }
        }
        for(int i=0;i<n;i++){
            for(int s=0;s<(1<<n);s++){
                f[x][i][s]=tmp[i][s];
                tmp[i][s]=0;
            }
        }
    }
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int u,v;
        cin>>u>>v;
        arv[u]|=(1<<v-1);
        arv[v]|=(1<<u-1);
    }
    for(int i=1;i<n;i++){
        int u,v;
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dp(1,0);
    int ans=0;
    for(int i=0;i<n;i++){
        FWT_or(f[1][i],-1);
        ans+=f[1][i][(1<<n)-1];
    }
    cout<<ans;
    return 0;
}
posted @ 2026-10-07 21:12  farenara_sayowell  阅读(6)  评论(0)    收藏  举报