[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;
}

浙公网安备 33010602011771号