题目描述
题解
刚开始的思路是f(i,j,s)表示以i为根的子树,i对应的点为j,其子树对应的点的状态为s的方案数
这样其实是可以dp的,但是时间爆炸啊有没有
事实上正确的思路是差不多的
f(i,j)表示以i为根的子树,i对应的点为j的方案数
这样的话会出来很多的合法的,就是很多点都对应到一个点去了,也就是说有一些点没有被对应到
那么容斥一下就好啦~
答案=至少0个点不选的方案数-至少一个点不选的方案数+至少两个点不选的方案数…
指数级枚举哪些点不选,然后每一次dp一下
实际上复杂度是
O(2nn3)
的,稍微有点不科学啊对不对,但是速度还是挺好的
代码
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define N 20
#define LL long long
int n,m,sta;
int tot,point[N],nxt[N*2],v[N*2];
int _tot,_point[N],_nxt[N*N],_v[N*N];
LL f[N][N],ans;
void _add(int x,int y)
{
++_tot; _nxt[_tot]=_point[x]; _point[x]=_tot; _v[_tot]=y;
}
void add(int x,int y)
{
++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
}
void treedp(int x,int fa)
{
bool flag=0;
for (int i=point[x];i;i=nxt[i])
if (v[i]!=fa)
treedp(v[i],x),flag=1;
if (!flag)
{
for (int i=1;i<=n;++i)
if (!((sta>>(i-1))&1)) f[x][i]=1;
return;
}
for (int i=1;i<=n;++i)
if (!((sta>>(i-1))&1))
{
f[x][i]=1;
for (int j=point[x];j;j=nxt[j])
if (v[j]!=fa)
{
LL now=0;
for (int k=_point[i];k;k=_nxt[k])
now+=f[v[j]][_v[k]];
f[x][i]*=now;
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=m;++i)
{
int x,y;scanf("%d%d",&x,&y);
_add(x,y),_add(y,x);
}
for (int i=1;i<n;++i)
{
int x,y;scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
for (sta=0;sta<1<<n;++sta)
{
int opt=0;
for (int i=0;i<n;++i)
if ((sta>>i)&1) ++opt;
memset(f,0,sizeof(f));
treedp(1,0);
LL now=0;
for (int i=1;i<=n;++i) now+=f[1][i];
if (opt&1) ans-=now;
else ans+=now;
}
printf("%lld\n",ans);
}