传送门
__debug神仙计数课件的容斥入门题,有丶遭不住。
首先根据题目可以想出一个错误的treedp的做法,设(f[u][x])表示以(u)为根的这棵子树中,以(u)为根,对应原图的(x)点的方案数,那么我们可以得到一个(O(n^3))的转移方法(枚举每个儿子以及其对应的点),但是这样需要去重,因为多个点可能在原图对应的点相同。然后考虑容斥,我们(2^n)枚举每个子集可以是哪些点被填,然后考虑每个子集会重复的部分,拿(<=n)个点的(sum_{i=1}^{n}f[1][i])减去(<=n-1)个点的加上(<=n-2)个点的...以此类推,画画韦恩图就很好理解了。
然后这题卡常,fuck出题人
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int N=20;
ll ans,f[N][N];
int g[N][N],t[N][N],s[N],n,m,tot;
void dfs(int u,int fa){
for(int i=1;i<=tot;i++)
f[u][s[i]]=1;
for(int i=1;i<=n;i++){
if(!t[u][i]||i==fa)continue;
dfs(i,u);
for(int j=1;j<=tot;j++){
ll sum=0;int x=s[j];
for(int k=1;k<=tot;k++)
if(g[x][s[k]])sum+=f[i][s[k]];
f[u][x]*=sum;
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,u,v;i<=m;i++)scanf("%d%d",&u,&v),g[u][v]=g[v][u]=1;
for(int i=1,u,v;i<n;i++)scanf("%d%d",&u,&v),t[u][v]=t[v][u]=1;
for(int S=0;S<(1<<n);S++){
tot=0;
for(int i=0;i<n;i++)if(S&(1<<i))s[++tot]=i+1;
dfs(1,0);
for(int i=1;i<=tot;i++)
ans+=((n-tot)&1?-1:1)*f[1][s[i]];
}
printf("%lld
",ans);
}