1.建立虚点:https://www.acwing.com/problem/content/288/
考虑再建立一点作为森林中每一个树的根的根,接下来就是经典的树上背包问题:
AC代码:
#include<bits/stdc++.h>
using namespace std;
int n,m;
int w[100010];
int h[100010],e[100010],ne[100010],idx;
int f[1001][1001];
void add(int a, int b) // 添加一条边a->b
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u){
for(int i=h[u];i!=-1;i=ne[i]){
dfs(e[i]);
for(int j=m-1;j;j--){
for(int k=1;k<=j;k++) f[u][j]=max(f[u][j],f[u][j-k]+f[e[i]][k]);
}
}
for(int i=m;i;i--) f[u][i]=f[u][i-1]+w[u];
}
int main(){
cin>>n>>m;
memset(h, -1, sizeof h);
for(int i=1;i<=n;i++){
int p;
cin>>p>>w[i];
add(p,i);
}
m++;
dfs(0);
cout<<f[0][m];
}
2.二次扫描,换根DP:https://www.acwing.com/problem/content/289/
首先,我们不妨以1为根节点,令表示以
为源点,从其流向子树的最大流量。
对于度为1(除根节点外)的赋为正无穷,另外的易得状态转移方程:
,其中
是
的子节点。
现在我们考虑换根(也就是把用父亲状态跟新儿子):
对于根节点的一个儿子
来说,我们记
表示它流向整个水系的最大流量。
我们考虑比较普通的情况:它由两部分组成:
1.它自己子树的流量 2.流过它父亲的最大流量。
易得:
不过我们还需考虑几个特殊情况:
1.的度为1,那么它的流量就直接是流过他父亲的最大流量:即上述右部分。
2.它的父亲度为1,那么它流向父亲的最大流量就是
事实上那个普通情况就是的情况。
AC代码:
#include<bits/stdc++.h>
using namespace std;
int t;
int x,y,z;
int w[200010],d[200010],f[200010];
int n;
struct node{
int dian,zhi;
};
vector<node> edge[200010];
int dg[201000];
void dfs1(int root,int fa){
if(dg[root]==1&&root!=1){
d[root]=1e9;
return;
}
d[root]=0;
for(int i=0;i<edge[root].size();i++){
int x=edge[root][i].dian;
if(x==fa) continue;
dfs1(x,root);
d[root]+=min(d[x],edge[root][i].zhi);
}
}
void dfs2(int root,int fa){
for(int i=0;i<edge[root].size();i++){
int x=edge[root][i].dian;
if(x==fa) continue;
if(dg[x]==1){
f[x]=f[root]-edge[root][i].zhi;
}
else if(dg[root]==1){
f[x]=d[x]+edge[root][i].zhi;
dfs2(x,root);
}
else{
f[x]=d[x]+min(edge[root][i].zhi,f[root]-min(edge[root][i].zhi,d[x]));
dfs2(x,root);
}
}
}
int main(){
cin>>t;
while(t--){
memset(edge,0,sizeof(edge));
memset(dg, 0, sizeof(dg));
cin>>n;
for(int i=1;i<=n-1;i++){
cin>>x>>y>>z;
dg[x]++,dg[y]++;
edge[x].push_back({y,z});
edge[y].push_back({x,z});
}
dfs1(1,-1);
f[1]=d[1];
dfs2(1,-1);
int res=0;
for(int i=1;i<=n;i++) res=max(res,f[i]);
cout<<res<<endl;;
}
}