把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷4815】[CCO2014] 狼人游戏(树形DP)

点此看题面

大致题意: 已知有平民和狼人共\(n\)个,每个平民会指控和保护任何人,每个狼人只会指控平民、保护狼人。告诉你\(m\)对指控与保护的关系,求有\(k\)个狼人的方案总数。

树形\(DP\)

这题目乍看很神仙,但一看给出的其他一些限制,就可以发现这就是一棵树!

而对于这种问题,容易想到用树形\(DP\)去求解。

我们可以设\(f_{i,0/1,j,0/1}\)来表示编号为\(i\)的节点作为平民/狼人时共有\(j\)个狼人的方案数,其中第二维分别表示上一次和这一次的答案,用了滚存

则显然可以推得\(DP\)方程如下:

\[f_{x,op,j+l,0}=\sum f_{x,op\text{^}1,j,0}*(f_{son,sz_{son}\&1,l,0}+f_{son,sz_{son}\&1,l,0}) \]

\[f_{x,op,j+l,1}=\sum f_{x,op\text{^}1,j,1}*f_{son,sz_{son}\&1,l,[val=='D']} \]

对于第一个方程,我们知道平民会指控和保护任何人,因此无论子节点为平民还是狼人都可以转移。

对于第二个方程,我们知道狼人只会指控平民、保护狼人,因此当指控关系时子节点必须为平民,当保护关系时子节点必须为狼人。

统计答案

由于这是一片森林,因此我们最后还需要统计答案,而这也是一个类似的过程。

我们可以设\(s_{0/1,j}\)来表示共有\(j\)个狼人的方案数,其中第一维类似于前面的第二位,分别表示上一次和这一次的答案。

若我们从\(0\)号节点向所有根节点连边,则转移式其实是几乎一样的:

\[s_{op,j+l}=\sum s_{op\text{^}1,j}*(f_{son,sz_{son}\&1,l,0}+f_{son,sz_{son}\&1,l,0}) \]

其实很好理解,这与上面的式子就少了枚举当前节点是平民还是狼人,而\(0\)号节点作为虚拟节点,显然都不是嘛。。。

具体实现详见代码。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<tepename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define RC Reg char
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200
#define X 1000000007
#define min(x,y) ((x)>(y)?(x):(y))
#define add(x,y,v) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].op=v)
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,m,k,ee,deg[N+5],lnk[N+5];struct edge {int to,nxt;char op;}e[N+5];
I int XSum(RI x,CI y) {return x+y>=X?x+y-X:x+y;}
class TreeDper//树形DP
{
    private:
        int s[2][N+5],t[N+5],g[N+5],f[N+5][2][N+5][2];
        I void DP(CI x)//DP
        {
            g[x]=f[x][0][0][0]=f[x][0][1][1]=1;for(RI i=lnk[x],j,l,op=1;i;i=e[i].nxt,op^=1)//初始化信息,枚举子节点
            {
                for(memset(f[x][op],0,sizeof(f[x][op])),DP(e[i].to),j=min(k,g[x]);~j;--j) for(l=min(k-j,g[e[i].to]);~l;--l)//枚举状态进行转移
                {
                    Inc(f[x][op][j+l][0],1LL*f[x][op^1][j][0]*XSum(f[e[i].to][t[e[i].to]][l][0],f[e[i].to][t[e[i].to]][l][1])%X),
                    Inc(f[x][op][j+l][1],1LL*f[x][op^1][j][1]*f[e[i].to][t[e[i].to]][l][e[i].op=='D']%X);
                }g[x]+=g[e[i].to],t[x]^=1;//更新size
            }
        }
    public:
        I int GetAns()//与上面的过程类似
        {
            RI i,j,l,op=1;for(s[0][0]=1,i=lnk[0];i;i=e[i].nxt,op^=1) 
                for(memset(s[op],0,sizeof(s[op])),DP(e[i].to),j=k;~j;--j) for(l=min(k-j,g[e[i].to]);~l;--l) 
                    Inc(s[op][j+l],1LL*s[op^1][j]*XSum(f[e[i].to][t[e[i].to]][l][0],f[e[i].to][t[e[i].to]][l][1])%X);
            return s[op^1][k];
        }
}T;
int main()
{
    ios::sync_with_stdio(false);
    RI i,x,y,ans=1;RC op;for(cin>>n>>k>>m,i=1;i<=m;++i) cin>>op>>x>>y,add(x,y,op),++deg[y];//读入+建边
    for(i=1;i<=n;++i) !deg[i]&&add(0,i,'*');return printf("%d",T.GetAns()),0;//求解并输出
}
posted @ 2019-03-12 07:36  TheLostWeak  阅读(279)  评论(0编辑  收藏  举报