bzoj 4455: [Zjoi2016]小星星 树形dp+容斥原理

题意

给出一棵树和一个图,问有多少种方法把树的节点标号使得其在改图中至少有一棵生成树与原来的树是重构的。
n<=17

分析

想到了容斥,但是没想到从哪里容斥。。。
显然题目给了两个限制,一个是原树中的每条边都要在图中出现,一个是每个标号仅出现一次。
我们可以在必须满足第一个限制的前提下,对第二个限制进行容斥。
假设现在知道了那些号是不能标的,那么我们就可以通过树形dp来得到方案数:设f[i,j]表示节点i标号为j的方案数,然后枚举其儿子的方案数,转移随便搞即可。
那么答案就是随便标的方案数-1个标号不能用的方案数+2个标号不能用的方案数。。。。
为什么呢?
因为每个标号不能重复,那么就要用全部情况减去有重复的情况。如果有一个标号不能选,那就表明必然至少有一个会重复,所以正确性显然。

总结

今天做了两道类似的题,一道是bzoj 4596: [Shoi2016]黑暗前的幻想乡,另一道就是这道啦。
方法都是容斥,那么我想总结一下类似题目的一般套路。
首先你得想得到要用容斥,可以记住某位大神所说的,看到计数想容斥。
然后找到题目所给的限制。bzoj 4596中的限制一个是必须是生成树,另一个是必须每条边属于不同的公司。这题的限制在上面。
那么我们就可以在满足题目所给的其余限制的前提下,对其中一个限制进行容斥,也就是先不管这个限制,再减去其不符合限制的答案即可。

代码

#include
#include
#include
#include
#include
using namespace std;

typedef long long LL;

const int N=20;

int n,m,cnt,a1,a[N],last[N],map[N][N];
struct edge{int to,next;}e[N*2];
LL ans,f[N][N];

void addedge(int u,int v)
{
    e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt;
    e[++cnt].to=u;e[cnt].next=last[v];last[v]=cnt;
}

void dp(int x,int fa,int s)
{
    for (int i=last[x];i;i=e[i].next)
        if (e[i].to!=fa) dp(e[i].to,x,s);
    for (int i=1;i<=a1;i++)
    {
        f[x][i]=1;
        for (int j=last[x];j;j=e[j].next)
        {
            if (e[j].to==fa) continue;
            LL w=0;
            for (int k=1;k<=a1;k++)
                if (map[a[i]][a[k]]) w+=f[e[j].to][k];
            f[x][i]*=w;
        }
    }
}

LL calc(int s)
{
    a1=0;
    for (int i=1;i<=n;i++) if (!(s&(1<<(i-1)))) a[++a1]=i;
    dp(1,0,s);
    LL ans=0;
    for (int i=1;i<=a1;i++) ans+=f[1][i];
    return ans;
}

void dfs(int x,int y,int s)
{
    if (x>n)
    {
        ans+=y*calc(s);
        return;
    }
    dfs(x+1,y,s);
    dfs(x+1,-y,s+(1<<(x-1)));
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        map[x][y]=map[y][x]=1;
    }
    for (int i=1;iint x,y;
        scanf("%d%d",&x,&y);
        addedge(x,y);
    }
    dfs(1,1,0);
    printf("%lld",ans);
    return 0;
}

你可能感兴趣的:(树形dp,容斥原理)