题目大意:
给一个N个点的森林,从中选取恰好M个点,当你选择一个点时,必须选择他的父亲节点。问能获得的最大点权和是多少。
数据范围见题目链接:https://www.luogu.org/problem/P2014
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define pb push_back
using namespace std;
const int N = 2e5+1000;
int n,m;
int dp[410][410],s[410],siz[410];
vector<int>nxt[410];
void dfs(int u) { //时间复杂度O(N*M)
siz[u] = 1;
dp[u][0] = 0;
dp[u][1] = s[u];
for(auto v:nxt[u]) {
dfs(v);
siz[u] += siz[v];
per(i, min(siz[u],m), 0) {
int P = min(siz[v],i-1); //这里因为题目限制,最多到i-1
rep(j, 0, P) { //如果在其他题中被卡常,可以先0,然后P->1,遇见-1状态直接break
if(dp[u][i-j]==-1) continue;
dp[u][i] = max(dp[u][i],dp[u][i-j]+dp[v][j]);
}
}
}
}
int main() {
//freopen("a.txt","r",stdin);
scanf("%d%d",&n,&m);
rep(i, 1, n) {
int f,x;
scanf("%d%d",&f,&x);
s[i] = x;
nxt[f].pb(i);
}
m++;
memset(dp,-1,sizeof(dp));
dfs(0);
printf("%d",dp[0][m]);
return 0;
}
考虑向后转移,dp方程有一种更优秀的写法
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define pb push_back
using namespace std;
const int N = 2e5+1000;
int n,m;
int dp[410][410],s[410],siz[410];
vector<int>nxt[410];
void dfs(int u) { //时间复杂度O(N*M)
siz[u] = 1;
dp[u][0] = 0;
dp[u][1] = s[u];
for(auto v:nxt[u]) {
dfs(v);
per(i, min(siz[u],m), 1) {
int P = min(siz[v],m-i);
per(j, P, 0)
dp[u][i+j] = max(dp[u][i+j],dp[u][i]+dp[v][j]);
}
siz[u] += siz[v];
}
}
int main() {
//freopen("a.txt","r",stdin);
scanf("%d%d",&n,&m);
rep(i, 1, n) {
int f,x;
scanf("%d%d",&f,&x);
s[i] = x;
nxt[f].pb(i);
}
m++;
dfs(0);
printf("%d",dp[0][m]);
return 0;
}