【ZJOI2016】小星星
题面
https://www.luogu.org/problem/P3349
题解
很好听的题目名字,很优美的思路。
设$f[x][y][S]$为树上的$x$点对应图上的$y$点,$x$的子树对应了图上$S$的子集。枚举子集转移即可。
但是这样大概是$O(3^nn^3)$的,是跑不过去的。
$\mbox{Gloid}$爷说如果状态记录了集合,转移就必须是$O(3^n)$了,考虑优化,
设$f[x][y]$为树上的$x$点对应图上的$y$点,但是这个状态图上的点和树上的点并不一一对应。一些点没有标号,一些标号对应了很多点。
可以容斥一下,只考虑$S$集合的点的标号,$+/-$交替即可。
写起来出奇好写,可是感觉不是那么好理解呢。
#include<vector> #include<cstdio> #include<cstring> #include<iostream> #define ri register int #define N 18 #define LL long long using namespace std; int n,m; int has[N][N]; vector<int> to[N]; inline int read() { int ret=0,f=0; char ch=getchar(); while (ch<'0' || ch>'9') f|=(ch=='-'),ch=getchar(); while (ch>='0' && ch<='9') ret*=10,ret+=ch-'0',ch=getchar(); return f?-ret:ret; } int v[1<<(N-1)]; LL f[N][N],ans=0; void dfs(int s,int x,int ff) { for (ri i=1;i<=n;i++) if (s&(1<<(i-1))) f[x][i]=1; for (ri i=0;i<to[x].size();i++) { int y=to[x][i]; if (y==ff) continue; dfs(s,y,x); for (ri j=1;j<=n;j++) if (s&(1<<(j-1))) { LL sum=0; for (ri j2=1;j2<=n;j2++) if (has[j][j2]) sum+=f[y][j2]; f[x][j]*=sum; } } } LL calc(int s) { memset(f,0,sizeof(f)); dfs(s,1,1); LL ret=0; for (ri i=1;i<=n;i++) ret+=f[1][i]; return ret; } int main() { n=read(); m=read(); for (ri i=1;i<=m;i++) { int u=read(),v=read(); has[u][v]=1,has[v][u]=1; } for (ri i=1;i<n;i++) { int u=read(),v=read(); to[u].push_back(v); to[v].push_back(u); } v[0]=0; for (ri i=1;i<(1<<n);i++) { v[i]=v[i-(i&(-i))]+1; if (v[i]%2==n%2) ans+=calc(i); else ans-=calc(i); } cout<<ans<<endl; }