2025“钉耙编程”中国大学生算法设计春季联赛(10)1007_小塔的魔法树
赛时写了一个\(O(nm^{2})\)的做法想不到该怎么优化,赛后看题解用dfs序dp,觉得有点不习惯(还是习惯直接树上dp),翻别人的代码发现一个直接树上dp的做法,这里仅根据我的理解来解释一下。
首先设dp[i][j]表示整棵树所选节点权值和为j的情况下,i节点必选的方案数,接下来看如何转移。
设当前节点u的父节点为fa,则很显然有dp[u][j] += dp[fa][j - w[u]],表示的就是正好选完当前u节点之后所选节点权值和就是j的方案。
再看如何从子节点合并。设u节点子节点为v,显然如果v必选的话,那么u也是必选的,因为从根节点到v节点一定会经过u。所以对于dp[u][j],我们直接把\(\sum dp[v][j]\)加上即可。最后的答案就是\(\sum dp[1][j]\),时间复杂度\(O(nm)\)
#include<bits/stdc++.h>
using namespace std;
long long t;
const int mod = 1e9 + 7;
const long long N = 2e5 + 10;
long long n,m;
long long a[N];
vector<vector<long long> > dp;
vector<long long> g[N];
void solve() {
for(long long i = 0;i <= n;i++) g[i].clear();
cin >> n >> m;
dp.assign(n + 10,vector<long long>(m + 10,0));
for(long long i = 1;i <= n;i++) cin >> a[i];
for(long long u,v,i = 1;i < n;i++) {
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dp[0][0] = 1;
function<void(int, int)> dfs = [&](int x, int y) {
//dp[i][j]表示的可能是,若整棵树所选择的节点权值之和正好为j的情况下i必选的方案数
for(int i = a[x]; i <= m; i++)
dp[x][i] = dp[y][i - a[x]];
//这里先统计从根节点到当前i节点所选的权值之和已经为j的方案数
for(auto to : g[x]){
if(to == y) continue;
dfs(to, x);
for(int i = 0; i <= m; i ++)
dp[x][i] = (dp[x][i] + dp[to][i]) % mod;
//若整棵树所选权值和正好为j并且当前u节点的儿子必选,则也意味着当前u节点必选,应该统计到当前dp[u][j]中
}
};
dfs(1,0);
long long ans = 0;
for(long long i = 0;i <= m;i++) ans += dp[1][i],ans %= mod;
cout << ans << '\n';
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin >> t;
while(t--) solve();
return 0;
}

浙公网安备 33010602011771号