bzoj 4455: [Zjoi2016]小星星

来源:互联网 发布:私募股权基金知乎 编辑:程序博客网 时间:2024/06/10 13:18
一种暴力做法是f[x][y][S]表示以i为根的子树,x匹配的是y,使用了的集合是S的方案数,然后转移显然,然而我并没有想到。
正解非常巧妙。
枚举集合S,要求所有点只能和S中的点配对,可以多个点匹配一个点,f[x][y]表示x匹配的是y,以x为根的子树的方案数,转移yy一下就行。
然后容斥 如果|S|&1 == n&1 那么ans+=sum 否则 ans-=sum
这道题告诉我,遇到枚举子集的dp,可以考虑容斥乱搞。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
 
#define ll long long
#define inf 1e9
#define eps 1e-8
#define md
#define N 20
using namespace std;
struct yts { int x,t,ne;} e[2*N];
int v[N],mo[1000010];
bool a[N][N];
ll f[N][N];
int q[N];
int n,num,w;
 
void put(int x,int y)
{
num++; e[num].x=x; e[num].t=y;
e[num].ne=v[x]; v[x]=num;
}
 
void dp(int x,int fa)
{
for (int j=1;j<=n;j++)
f[x][q[j]]=1;
for (int i=v[x];i;i=e[i].ne)
{
int y=e[i].t;
if (y!=fa)
{
dp(y,x);
for (int j=1;j<=w;j++)
{
ll ans=0;
for (int k=1;k<=w;k++)
if (a[q[j]][q[k]])
ans+=f[y][q[k]];
f[x][q[j]]*=ans;
}
}
}
}
 
void bitget(int S)
{
w=0;
for (int i=1;i<=n;i++)
{
if (S&1) q[++w]=i;
S>>=1;
}
}
 
int main()
{
int m;
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
a[x][y]=a[y][x]=1;
}
for (int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
put(x,y); put(y,x);
}
ll ans=0;
for (int S=1;S<(1<<n);S++)
{
bitget(S);
memset(f,0,sizeof(f)); dp(1,0);
ll sum=0;
for (int i=1;i<=w;i++) sum+=f[1][q[i]];
if ((w&1)==(n&1)) ans+=sum;
else ans-=sum;
}
printf("%lld\n",ans);
return 0;
}

0 0