Luogu P4815 [CCO 2014] 狼人游戏 题解 [ 蓝 ] [ 树形背包 DP ] [ 计数 ]

狼人游戏:简单树形背包 DP,不懂为啥 \(n\le 200\),明明这题可以直接开到 \(n\le 5000\) 的。

观察题目给的特殊性质,把指认和给金水的操作转化为有向图,不难发现可以有如下性质:

  • 该有向图无环。
  • 每个点的入度最多为 \(1\)
  • 平民的出边没有任何作用,狼人的出边可以确定其他人的身份。

根据前两条性质,可以发现图的形态一定是一片外向树森林

因为狼人恰好为 \(w\) 个,而父节点的身份决定了子节点可能的身份,于是设计树形背包 \(dp_{i,j,0/1}\) 表示考虑到节点 \(i\) 时,子树内出了 \(j\) 个狼人,且 \(i\) 为平民 / 狼人的总方案数。转移利用乘法原理:

  • \(dp_{i,j,0}=\sum dp_{i,j-k,0}\times (dp_{v,k,0}+dp_{v,k,0})\)
  • \(dp_{i,j,1}=\sum dp_{i,j-k,1}\times dp_{v,k,type_v}\)

其中 \(type_v\) 表示 \(i\) 确定为狼人后 \(v\) 的身份。

直接枚举 \(j,k\) 转移是 \(O(n^3)\) 的,考虑树形背包的常见优化,控制 \(j,k\) 的上下界,根据子树大小动态调整枚举范围,让每个点对都在 LCA 处合并即可做到 \(O(n^2)\) 的复杂度。具体上下界见代码。另外注意背包的转移限制,为了防止转移的变量被提前更新,每次计算 DP 值的时候可以开一个辅助变量进行统计。

注意,由于图是森林,所以可以建一个虚拟节点连接所有的树,答案即为 \(dp_{root,w,0}\),因为虚拟节点为平民时不会对原树的根有任何限制。

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
const ll mod=1000000007;
const int N=205;
int n,w,m,tp[N],rd[N],sz[N];
ll dp[N][N][2];
vector<int>g[N];
void dfs(int u)
{
    sz[u]=1;dp[u][0][0]=dp[u][1][1]=1;
    for(auto v:g[u])
    {
        dfs(v);
        sz[u]+=sz[v];
        for(int i=min(sz[u],w);i>=0;i--)
        {
            ll v0=0,v1=0;
            for(int j=max(0,i-min(sz[u]-sz[v],w));j<=min(sz[v],i);j++)
            {
                v0=(v0+dp[u][i-j][0]*(dp[v][j][0]+dp[v][j][1]%mod)%mod)%mod;
                v1=(v1+dp[u][i-j][1]*dp[v][j][tp[v]]%mod)%mod;
            }
            dp[u][i][0]=v0;
            dp[u][i][1]=v1;
        }
    }
}
int main()
{
    //freopen("sample.in","r",stdin);
    //freopen("sample.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>w>>m;
    while(m--)
    {
        int u,v;char c;
        cin>>c>>u>>v;
        if(c=='A')tp[v]=0;
        else tp[v]=1;
        g[u].push_back(v);
        rd[v]++;
    }
    for(int i=1;i<=n;i++)
        if(rd[i]==0)
            g[n+1].push_back(i);
    dfs(n+1);
    cout<<dp[n+1][w][0];
    return 0;
}
posted @ 2025-06-06 22:03  KS_Fszha  阅读(8)  评论(0)    收藏  举报