[BZOJ4455][Zjoi2016]小星星(容斥原理+树形dp)

来源:互联网 发布:steam淘宝充值卡便宜 编辑:程序博客网 时间:2024/05/28 15:58

题目描述

传送门

题解

刚开始的思路是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 longint 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);}
0 0