这题想了好久,唯一想到比较有用的就是假设
d
p
[
u
]
[
j
]
dp[u][j]
dp[u][j]表示以u为根结点的子树花费j元最多可以提供多少用户,然后就没有然后了,都不知道怎么转移,看了学长巨巨的思路后发现新大陆,原来还可以利用换意的方式进行dp,即转台这样定义:
d
p
[
u
]
[
i
]
dp[u][i]
dp[u][i]表示以u为根结点的子树提供j个用户最多能赚多少钱,这样转移又方便,最后的答案就是
d
p
[
1
]
[
i
]
dp[1][i]
dp[1][i]中大于等于0的最大i
转移方程:
d
p
[
u
]
[
i
+
j
]
=
m
a
x
{
d
p
[
u
]
[
i
]
+
d
p
[
s
o
n
]
[
j
]
−
w
}
dp[u][i + j] = max\{ dp[u][i] + dp[son][j] - w\}
dp[u][i+j]=max{dp[u][i]+dp[son][j]−w}
其余细节看代码注释
ac代码
#include<cstdio>#include<cstring>#include<algorithm>#include<vector>#include<cctype>inlinelonglongIO(){longlong x =0;bool f =false;char c =getchar();while(!isdigit(c)){if(c =='-') f =true;
c =getchar();}while(isdigit(c)){
x =(x <<1)+(x <<3)+(c -'0');
c =getchar();}return f ?-x : x;}#define ll long longusingnamespace std;constint M =3e3+5;constint maxn =3e3+5, maxm =3e3+5;const ll inf =0xffffffff;int head[maxn], cnt;//初始化inlinevoidinit(){memset(head,-1,sizeof head); cnt =-1;}struct edges {int to, next;
ll w;}edge[maxm <<1];//无向图则需要乘2inlinevoidadd(int u,int v, ll w){
edge[++cnt]={.to = v,.next = head[u],.w = w};
head[u]= cnt;}
ll mon[M], sum, dp[M][M], son[M], tmp[M];int n, m;voiddfs(int u){if(u > n - m) son[u]=1, dp[u][1]= mon[u];// 如果是叶子节点则赚的钱就是他自己, 且叶子个数为1
dp[u][0]=0;for(int i = head[u];~i; i = edge[i].next){int v = edge[i].to;dfs(v);for(int j =1; j <= son[u]+ son[v];++j) tmp[j]=-inf;// 防止转移时覆盖,初始化tmp为负无穷,当然用01背包的方式逆向循环也可以for(int j =0; j <= son[u];++j){for(int k =0; k <= son[v];++k){
ll w =(k ==0?0: edge[i].w);// 如果子树一个叶子都不选当然不用减去子树的拼接边(回忆述树上背包模型)
tmp[j + k]=max(tmp[j + k], dp[u][j]+ dp[v][k]- w);}}
son[u]+= son[v];for(int j =1; j <= son[u];++j) dp[u][j]= tmp[j];}}intmain(){
n =IO(), m =IO();init();for(int i =1; i <= n - m;++i){int k =IO();while(k--){int a =IO(), c =IO();add(i, a, c);}}memset(dp,0xfe,sizeof dp);// 初始化为最小值for(int i = n - m +1; i <= n;++i) mon[i]=IO();dfs(1);int ans =0;for(int i =1; i <= m;++i){// printf("%lld\n", dp[1][i]);if(dp[1][i]>=0) ans =max(ans, i);// 寻找最大值i}printf("%d", ans);return0;}