【BZOJ 4455】【ZJOI 2016】小星星【容斥+树型DP】

Description

将一棵大小为n的树嵌入一个大小为n的图中,求方案数。(n<=18)

Solution

【官方题解】 :JudgeOnline/upload/201603/4455.txt

【我的理解】:
  我们先考虑将树中的点用图中的点来代替,因为共有18个点,每次枚举每个图中的点能不能用,即从图中取出一个点集 S ,共有O(2n)种取法。然后我们求出在这个点集映射的树有多少的是合法的。
  
  但是,显然,我们要求的答案是每个点一一映射,然后仔细思考一下,发现可以用容斥:
  
  计算任意标号(图中所有点随便选)的方案数 - sigma 没有标号i,剩余号任意标(也就相当于本来该标i的位置被标了一个和别的点重复的号,标号时i这个点没有出现在树中)的方案数+sigma没有标号i和j,剩余的号任意标的方案数……
  
  这样思路正确了,可对于每一种点集,又该怎么求合法的方案数呢?
  用树型DP。
  
  设 f[i][j][S] 表示点集为S,第i个点标j的方案。枚举每个点的标号和每个点的儿子的标号转移即可。每个对应的S,sigma{ f[1][i] }就是要求的值。这样跑了一个 O(n3) 的DP,总复杂度为 O(2nn3)

Code

#include<cstdio>
#include<cstring>
#include<cmath>

#define N 20
using namespace std;
typedef long long ll;

struct node{int to,next;}e[N*2];
int head[N],cnt,a[N],tot;
bool p[N][N];
ll f[N][N],ans;

void add_edge(int from,int to)
{
    e[++cnt].next = head[from];
    head[from] = cnt;
    e[cnt].to = to;
}

void dp(int v,int fa)
{
    for(int i = head[v];i;i=e[i].next)
        if(e[i].to != fa) dp(e[i].to,v);

    for(int i = 1;i <= tot;i++) {
        f[v][i] = 1;
        for(int j = head[v];j;j=e[j].next)
            if(e[j].to != fa) {
                ll t = 0;
                for(int k = 1;k <= tot;k++)
                    if( p[a[i]][a[k]] ) t += f[e[j].to][k];
                f[v][i] *= t;
            }
    }
}

int main()
{
    int n,m,u,v;
    scanf("%d%d",&n,&m);
    memset(p,0,sizeof(p));
    for(int i = 1;i <= m;i++) {
        scanf("%d%d",&u,&v);
        p[u][v] = p[v][u] = 1;
    }

    cnt = 0;
    memset(head,0,sizeof(head));
    for(int i = 1;i < n;i++) {
        scanf("%d%d",&u,&v);
        add_edge(u,v); add_edge(v,u);
    }

    int nn = 1 << n;
    ans = 0;
    for(int i = 1;i < nn;i++)
    {
        tot = 0;
        for(int j = 1;j <= n;j++)
            if( (i>>(j-1))&1) a[++tot] = j;
        dp(1,0);
        ll s = 0;
        for(int j = 1;j <= tot;j++)
            s += f[1][j];
        ans += s * (ll)pow(-1,n-tot);
    }
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值