NC20811 蓝魔法师 (树形DP/树上01背包)

NC20811 蓝魔法师 (树形DP/树上01背包)

题目链接 学习博客

题目:

给出一棵树,求有多少种删边方案,使得删后的图每个连通块大小小于等于k,两种方案不同当且仅当存在一条边在一个方案中被删除,而在另一个方案中未被删除,答案对998244353取模 。

题解:

没想到是树形DP题,做题太少了 。这类树上选一个子图/连通块,给一些限制问方案数/子图权值最大值一般可以先想想树形DP。

这道题就可以在处理u点时,把u看作一个树上连通块的根,它的大小来自选了多少子节点为根的联通块。所以相当于对u的每个子节点选与不选的考虑,来构成u为根一定大小的连通块,可以用树上01背包来做。

\(dp[u][j]\) 表示u为根的大小为 j 的连通块方案数。显然对每个子节点 v 有以选与不选两种更新情况。

  1. 选v

    \[dp[u][j] = dp[u][j-x]*dp[v][x] \]

  2. 不选v: v的所有情况与u独立,直接乘上sum就行。

    \[dp[u][j]=dp[u][j]*sum \\ 其中sum=\sum_{i=1}^{min(k,siz[v])}dp[v][i] \]

最后 \(res = \sum_{i=1}^{k}dp[1][i]\)

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#include<queue>
#include<vector>
#include<string>
#include<fstream>
using namespace std;
#define rep(i, a, n) for(int i = a; i <= n; ++ i)
#define per(i, a, n) for(int i = n; i >= a; -- i)
typedef long long ll;
const int N = 2e3 + 15;
const int mod = 998244353;
const double Pi = acos(- 1.0);
const int INF = 0x3f3f3f3f;
const int G = 3, Gi = 332748118;
ll qpow(ll a, ll b) { ll res = 1; while(b){ if(b & 1) res = (res * a) % mod; a = (a * a) % mod; b >>= 1;} return res; }
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
//

int n, k;
int dp[N][N], siz[N];
vector<int> g[N];

void dfs(int u, int pre){
    siz[u] = 1; dp[u][1] = 1;
    for(int i = 0; i < g[u].size(); ++ i){
        int v = g[u][i];
        if(v == pre) continue;
        dfs(v, u);
        
        int sum = 0;
        for(int j = 1; j <= min(k, siz[v]); ++ j) sum = (sum + dp[v][j]) % mod;
        for(int j = min(k, siz[u]); j >= 1; -- j){
            for(int z = min(k, siz[v]); z >= 1; -- z){
                if(j + z <= k)
                    dp[u][j + z] = (dp[u][j + z] + 1ll * dp[u][j] * dp[v][z]) % mod;
            }
            dp[u][j] = (1ll * dp[u][j] * sum) % mod;
        }
        siz[u] += siz[v];
    }
}

int main()
{
    scanf("%d%d",&n,&k);
    for(int i = 1; i < n; ++ i){
        int x, y; scanf("%d%d",&x,&y);
        g[x].push_back(y); g[y].push_back(x);
    }
    dfs(1, 0);
    
    int res = 0;
    for(int i = 1; i <= k; ++ i) res = (res + dp[1][i]) % mod;
    printf("%d\n",res);
    return 0;
}
posted @ 2020-08-06 10:17  A_sc  阅读(166)  评论(0编辑  收藏  举报