树上方案数统计—总结

例题1

题意

(洛谷4084 [USACO17DEC]Barn Painting)
给定一颗N个节点组成的树,3种颜色,其中K个节点已染色,要求任意两相邻节点颜色不同,求合法染色方案数。

题解

树形计数类DP
简单粗暴的,设f[x][t]表示将x染色为t时,x这棵子树的方案数。

那么就有f[x][t]=\prod _{y\in son[x]} \sum_{k\neq t}f[y][k]

初始化f[x][t]=1,特别的,当x已被染色为t时,f[x][k]=0(k!=t)。
答案为\sum_{i=0}^{2} f[x][i]

小结

树上DP求方案数的特点是对于x求出不包含x的子树的方案,因为子树间互不相干,所以将x的子节点的子树 所有方案之和 乘起来就是 x子树的方案数 了。对于x=root,就是我们要求的总方案数。请注意其中加法原理和乘法原理的穿插运用。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int MAXN=1e5+10;

int n,K;
int a[MAXN];

struct E{int y,next;}e[MAXN*2];int len=1,last[MAXN];
void ins(int x,int y)
{
    e[++len]=(E){y,last[x]};last[x]=len;
}

ll f[MAXN][3];
void dfs(int x,int fa)
{
    if(a[x]==0) f[x][0]=f[x][1]=f[x][2]=1;
    else f[x][0]=f[x][1]=f[x][2]=0,f[x][a[x]-1]=1;
    for(int k=last[x];k;k=e[k].next)
    {
        int y=e[k].y;
        if(y==fa) continue;
        dfs(y,x);
        for(int i=0;i<3;i++) f[x][i]=f[x][i]*(f[y][(i+1)%3]+f[y][(i+2)%3])%mod;
    }
}

int main()
{
    scanf("%d%d",&n,&K);
    for(int i=1;i<n;i++)
    {
        int x,y;scanf("%d%d",&x,&y);
        ins(x,y);ins(y,x);
    }
    for(int i=1;i<=K;i++)
    {
        int x,c;scanf("%d%d",&x,&c);
        a[x]=c;
    }
    dfs(1,0);
    printf("%lld\n",(f[1][0]+f[1][1]+f[1][2])%mod);
    return 0;
}


例题2

题目

(洛谷3349 [ZJOI2016]小星星)
小Y是一个心灵手巧的女孩子,她喜欢手工制作一些小饰品。她有n颗小星星,用m条彩色的细线串了起来,每条细线连着两颗小星星。
有一天她发现,她的饰品被破坏了,很多细线都被拆掉了。这个饰品只剩下了n?1条细线,但通过这些细线,这颗小星星还是被串在一起,也就是这些小星星通过这些细线形成了树。小Y找到了这个饰品的设计图纸,她想知道现在饰品中的小星星对应着原来图纸上的哪些小星星。如果现在饰品中两颗小星星有细线相连,那么要求对应的小星星原来的图纸上也有细线相连。小Y想知道有多少种可能的对应方式。
只有你告诉了她正确的答案,她才会把小饰品做为礼物送给你呢。

题解

树形计数类DP+容斥
老套路设f[x][u]表示x子树中,x映射到原图中u这个点的方案数。

转移也很像,f[x][u]=\prod_{y\in x}\sum_{v\subseteq u} f[y][u]*ma[u][v]

当前点集,总方案为\sum_{i=1}^{n} f[root][i],即root映射到任意点的方案数之和。

意思是x对应u点,y对应v点,如果u和v相连,那么这一部分可以算为一种方案。
容易发现状态是重复计算了的,所以要容斥一下。枚举点集的所有状态,记录点集大小,如果与n的差为偶则加,为奇则减。
这题很卡常,如果T了一定要参考代码实现。主要是要省掉统计点集大小这一步,建议用dfs来枚举所有状态。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=20;

inline int read()
{
    int re=0;char ch=getchar();
    while(ch<'0' || ch>'9') ch=getchar();
    while(ch>='0' && ch<='9') re=re*10+(ch^48),ch=getchar();
    return re;
}

int n,m,ma[MAXN][MAXN];

struct E{int y,next;}e[MAXN*2];int len=1,last[MAXN];
void ins(int x,int y)
{
    e[++len]=(E){y,last[x]};last[x]=len;
}

int num[MAXN];int tot=0,ve[MAXN];
ll ans,f[MAXN][MAXN];
inline void dfs(int x,int fa)
{
    for(int i=1;i<=n;i++) f[x][i]=1;
    for(int k=last[x];k;k=e[k].next)
    {
        int y=e[k].y;
        if(y==fa) continue;
        dfs(y,x);
        for(int i=1,u=ve[1];i<=tot;u=ve[++i])
        {
            ll sum=0;//公式往这里套
            for(int j=1,v=ve[1];j<=tot;v=ve[++j]) if(ma[u][v]) sum+=f[y][v];
            f[x][u]*=sum;
        }
    }
}

inline void work(int k,int cnt)
{
    if(k>n)
    {
        dfs(1,0);
        ll sum=0;
        for(int i=1;i<=n;i++) sum+=f[1][i];
        if(n-cnt&1) ans-=sum;else ans+=sum;
    }
    else
    {
        num[k]=0;work(k+1,cnt);
        num[k]=1;ve[++tot]=k;work(k+1,cnt+1);tot--;//ve记录点集中的点
    }
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        ma[x][y]=ma[y][x]=1;
    }
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read();
        ins(x,y);ins(y,x);
    }
    /*for(int i=1,imax=1<<n;i<imax;i++)
    {
        int cnt=0;ll sum=0;
        for(int j=1,k=i;j<=n;j++,k>>=1) num[j]=k&1,cnt+=num[j];//这种做法相当慢
        dfs(1,0);
        for(int j=1;j<=n;j++) sum+=f[1][j];
        if(n-cnt&1) ans-=sum;else ans+=sum;
    }*/
    work(1,0);
    printf("%lld\n",ans);
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值