P4516 [JSOI2018] 潜入行动 题解【树上动态规划入门】

问题简述

现有一个\(n\)个节点的树。你要在这个树上的恰好 \(k\) 个节点打上标记。

问有多少种不同的方案,使得对于任意一个点 \(u\) ,总有与 \(u\) 之间相邻的点且该点被打上标记。

输出答案对 \(10^9+7\) 取模。

\(n \in [1,10^5], k\in [1,100]\)

思考

树上计数问题,考虑动态规划。

由于存在限制条件:

使得对于任意一个点 \(u\) ,总有与 \(u\) 之间相邻的点且该点被打上标记。

所以,我们称一个点存在相连的被标记点为这个点被覆盖。反之。

考虑 \(dp\) 结构

很显然,我们的动态规划数组 \(dp\) 至少有两个维度

  • \(u\) 代表这是那个子树,\(u\) 为这个子树的根节点

  • \(i\) 代表在这个子树内,放了多少个标记

\(dp[u][i]\)

考虑如何转移。

注意到,我们在当前子树的根节点是否打上标记,取决于是否有子结点还没有被覆盖。因为诺如果有子结点还没有被覆盖,那么我们这个位置必须打上标记,所以我们要记录每个节点是否已经被覆盖

同时,根节点是否被覆盖,取决于是否有子结点被标记。

由此可得,我们需要增加两个 \(dp\) 维度

  • 子树的根节点 \(u\) 是否被覆盖

  • 子树的根节点 \(u\) 是否打上标记。

由此可得我们最终的 \(dp\) 状态

\[dp[u][i][0/1][0/1] \]

考虑如何转移

很显然,每个子树/结点的 \(dp\) 状态由其多个子树的状态合并。所以我们要从一个没有子树加入的初始状态,一步步扩展到所有子树均加入的最终状态。说白了就是背包dp

我们考虑初始状态。初始时,没有任何子树的加入。所以:

    dp[u][1][1][0]=1;
    dp[u][0][0][0]=1;

我们尝试之间在\(dp\)数组上跑背包,但是我们发现一个问题。如果只使用\(dp\)数组,那么我们在转移时,左值就同时有之前子树的计算结果和当前子树的目前计算结果。这导致重复的计算。

所以,对于每个子树\(v\),我们要先建立一个辅助动态规划数组 \(f\) , 然后利用现有的 \(dp[v],dp[u]\) 来计算 \(f\) 的状态,然后再将 \(f\) 的内容拷贝到 \(dp[u]\)

具体的转移式子自己推,没什么难得。

代码

#include <bits/stdc++.h>

using namespace std;

typedef unsigned int ll;

const ll inf=1e18;
const ll mod=1e9+7;
const ll maxn=1e5+5;
const ll maxk=105;

inline ll read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = x * 10 + (ch - '0');
        ch = getchar();
    }
    return x * f;
}

struct Edge{
    ll v,nxt;
};

ll f[maxk][2][2]; //f[i][j]代表不
Edge e[maxn<<1];
ll fa[maxn],sz[maxn];
ll head[maxn];
ll dp[maxn][maxk][2][2]; //在以i为根的子树,放了j个,i放没放,i有没有被覆盖
ll n,k,etot;

void add_edge(ll u,ll v) {
    ++etot;
    e[etot]={v,head[u]};
    head[u]=etot;
    return;
}

void dfs(ll u,ll father) {
    sz[u]=1;
    fa[u]=father;
    for(ll i=head[u];i;i=e[i].nxt) {
        if(e[i].v==fa[u]) {
            continue;
        }
        dfs(e[i].v,u);
        sz[u]+=sz[e[i].v];
    }
    return;
}

void work(ll u) {
    dp[u][1][1][0]=1;
    dp[u][0][0][0]=1;
    if(sz[u]==1) {
        return;
    }

    memset(f,0,sizeof(f));
    ll szsum=1;
    for(ll i=head[u];i;i=e[i].nxt) {
        if(e[i].v==fa[u]) {
            continue;
        }
        ll v=e[i].v;
        work(v);
        //这个节点放or不放
        //memset(f,0,sizeof(f));
        ll oldsz=szsum;
        szsum+=sz[v];
        for(ll i=0;i<=min(k,szsum);++i) {
            f[i][0][0]=f[i][0][1]=f[i][1][0]=f[i][1][1]=0;
        }
        for(ll j=0;j<=min(k,oldsz);++j) {
            //总共k。
            for(ll h=0;h<=min(k-j,sz[v]);++h) {
                //这个节点放
                //这个节点不放

                //不放  覆盖
                //v必须被覆盖
                f[j+h][0][1]+=1LL * dp[v][h][0][1]*dp[u][j][0][1]%mod;
                //f[j][0][1]%=mod;
                f[j+h][0][1]+=(1LL * dp[v][h][1][1]*dp[u][j][0][1]%mod+1LL * dp[v][h][1][1]*dp[u][j][0][0]%mod)%mod;
                f[j+h][0][1]%=mod;

                //不放 不覆盖
                f[j+h][0][0]+=1LL * dp[v][h][0][1]*dp[u][j][0][0]%mod;
                f[j+h][0][0]%=mod;
                //u可以放
                //第一个0/1代表放没放  第二个代表是否被覆盖
                //放且覆盖
                f[j+h][1][1]+=1LL * dp[v][h][1][1]*dp[u][j][1][0]%mod;
                //f[j][1][1]%=mod;
                f[j+h][1][1]+=1LL * dp[v][h][1][1]*dp[u][j][1][1]%mod;
                f[j+h][1][1]%=mod;
                f[j+h][1][1]+=1LL * dp[v][h][1][0]*dp[u][j][1][0]%mod;
                //f[j][1][1]%=mod;
                f[j+h][1][1]+=1LL * dp[v][h][1][0]*dp[u][j][1][1]%mod;
                f[j+h][1][1]%=mod;
                f[j+h][1][1]+=1LL * dp[v][h][0][1]*dp[u][j][1][1]%mod;
                //f[j][1][1]%=mod;
                f[j+h][1][1]+=1LL * dp[v][h][0][0]*dp[u][j][1][1]%mod;
                f[j+h][1][1]%=mod;

                //放但是不覆盖
                f[j+h][1][0]+=1LL * dp[v][h][0][0]*dp[u][j][1][0]%mod;
                //f[j][1][0]%=mod;
                f[j+h][1][0]+=1LL * dp[v][h][0][1]*dp[u][j][1][0]%mod;
                f[j+h][1][0]%=mod;

            }

        }
        for(ll j=0;j<=min(k,szsum);j++)
        {
            dp[u][j][0][0]=f[j][0][0];
            dp[u][j][0][1]=f[j][0][1];
            dp[u][j][1][0]=f[j][1][0];
            dp[u][j][1][1]=f[j][1][1];
        }
    }
    return;
}

int main() {
    //freopen("input.txt","r",stdin);

    n=read();k=read();

    for(ll i=1;i<n;++i) {
        ll u,v;
        u=read();v=read();
        add_edge(u,v);
        add_edge(v,u);
    }

    dfs(1,0);

    work(1);

    printf("%lld\n",(dp[1][k][0][1]+dp[1][k][1][1])%mod);

    return 0;
}

实现细节

这题的数据很恶心,而且实现细节非常多。

咕咕咕,太晚了,先放着。明天继续写

posted @ 2026-08-05 23:49  txp2025  阅读(6)  评论(0)    收藏  举报