杭电多校10 : 1007 小塔的魔法树
感觉这是一道值得收藏的题目。
比赛的时候这道题看到后没有立即想到思路,虽然不算很难,但还是比较初见杀的。
比较常见的思路是采用树形dp,用 d p [ x ] [ y ] dp[x][y] dp[x][y]表示点 x x x的子树中选择了和为 y y y的联通块的方案数。
这样将两个合并是
d p ′ [ x ] [ t ] = ∑ i = 0 t d p [ x ] [ i ] × d p [ y ] [ t − i ] dp'[x][t]=\sum_{i=0}^tdp[x][i]\times dp[y][t-i] dp′[x][t]=i=0∑tdp[x][i]×dp[y][t−i]
这样的转移是 O ( n log n ) O(n\log n) O(nlogn)的。
感觉树形dp主要的问题是不能很好的利用只有一个连通块的条件。
后面想着如果是一个连通块,直接用在dfs序上面找不选的点就好。
令 d p [ i ] [ j ] dp[i][j] dp[i][j]表示在dfs序为 i i i的点上和为 j j j的方案数(只是到 i i i并没有决定 i i i选不选)
那么就有两种方案,如果选择 i i i,就把 d p [ i ] dp[i] dp[i]加到 d p [ i + 1 ] dp[i+1] dp[i+1];如果不选择 i i i,那么其子树就不用再去遍历,直接给 d p [ i + s i z [ i ] ] dp[i+siz[i]] dp[i+siz[i]]加上 d p [ i ] dp[i] dp[i]即可。
#include <bits/stdc++.h>
#define ll long long
using namespace std;const int N = 5e3 + 10, mod = 1e9 + 7;int n, m, a[N], f[N][N];
vector<int> e[N];
int z, pos[N], L[N], R[N];void dfs(int x, int fa) {L[x] = ++z;pos[z] = x;for (int y : e[x]) {if (y == fa) continue;dfs(y, x);}R[x] = z;
}void ad(int &x, int y) {x = x + y >= mod ? x + y - mod : x + y;
}void solve() {cin >> n >> m;memset(f, 0, sizeof(f));for (int i = 1; i <= n; i++) {cin >> a[i];e[i].clear();}for (int i = 1; i < n; i++) {int x, y;cin >> x >> y;e[x].push_back(y);e[y].push_back(x);}z = 0; dfs(1, 0);f[1][0] = 1;for (int i = 1; i <= n; i++) {//选择for (int j = 0; j + a[pos[i]] <= m; j++) {ad(f[i + 1][j + a[pos[i]]], f[i][j]);}//不选择if (i > 1) {int k = R[pos[i]] + 1;for (int j = 0; j <= m; j++) {ad(f[k][j], f[i][j]);}}}int ans = 0;for (int i = 0; i <= m; i++) {ad(ans, f[n + 1][i]);}cout << ans << "\n";
}int main() {// freopen("in.in", "r", stdin);ios::sync_with_stdio(false);cin.tie(nullptr);int T; cin >> T;while (T--) {solve();}
}