【BZOJ4455】小星星(ZJOI2016)-树形DP+容斥原理

测试地址:小星星
做法:本题需要用到树形DP+容斥原理。
我省省队队长Mychael曰:假紫题,水题。orz。
这题要求的是,对一棵树上每个点求一个映射,使得每一条树边在映射到一个图上后仍存在,求方案数。容易想到以下状态定义:
dp(i,j,k)为以i为根的子树,i映射到j,并且子树中的点映射到的点集为k的方案数。
然后就可以转移了。然而这个DP是O(n33n)的,无法通过此题,所以我们要想一个新的办法。
注意到我们把k一维略掉后,求出的方案数是没有“两个不同的点映射到的点不同”这一限制的方案数。这个条件实际上等价于原图中的每个点都要被映射到,于是想到容斥,枚举一个点集并强制这个点集内的点不能被映射到,然后做一遍上面的DP,按照容斥的式子计算即可。这样时间复杂度就是O(n32n)的,开O2可以过掉此题。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,first[20]={0},firstt[20]={0},tot=0;
ll dp[20][20];
struct edge
{
    int v,next;
}e[1000],t[1000];
void insert(int a,int b) {e[++tot].v=b,e[tot].next=first[a],first[a]=tot;}
void insertt(int a,int b) {t[++tot].v=b,t[tot].next=firstt[a],firstt[a]=tot;}

void DP(int now,int v,int fa)
{
    for(int i=firstt[v];i;i=t[i].next)
        if (t[i].v!=fa) DP(now,t[i].v,v);
    for(int i=1;i<=n;i++)
        if (now&(1<<(i-1)))
        {
            dp[v][i]=1;
            for(int p=firstt[v];p;p=t[p].next)
                if (t[p].v!=fa)
                {
                    ll sum=0;
                    for(int q=first[i];q;q=e[q].next)
                        if (now&(1<<(e[q].v-1))) sum+=dp[t[p].v][e[q].v];
                    dp[v][i]*=sum;
                }
        }
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        insert(a,b),insert(b,a);
    }
    tot=0;
    for(int i=1;i<n;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        insertt(a,b),insertt(b,a);
    }

    ll ans=0;
    for(int i=1;i<(1<<n);i++)
    {
        DP(i,1,0);
        int x=i,tot=0;
        while(x)
        {
            if (x&1) tot++;
            x>>=1;
        }
        ll f=1;
        if ((n-tot)%2) f=-1;
        for(int j=1;j<=n;j++)
            if (i&(1<<(j-1))) ans+=f*dp[1][j];
    }
    printf("%lld",ans);

    return 0;
}
posted @ 2018-05-16 16:02  Maxwei_wzj  阅读(137)  评论(0编辑  收藏  举报