Luogu2014
其实一眼就秒掉了这个树形dp,然后想了想转移,然而有些细节还是调了很久
首先整个图不一定是联通的,那么我们考虑把森林转化为树,即建立一个”源点“,然后把0向所有入度为0的点加边,就变成了一棵树。
我们又发现一个节点可能有多个儿子,不好处理,于是我们可以用一个惯用的方法——多叉树转二叉树,也可以叫左儿子右兄弟表示。顾名思义,新树节点的左儿子是原树的儿子,新树节点的右儿子是这个节点的兄弟,即拥有相同的父亲。那么我们就可以跑DP了。
我们可以这样考虑:设
dp[i][j]
表示在i号节点及其儿子还有右兄弟(也就是其右下部分)中选出j节课所获得的最大收益
1. 学自己与不学自己
2. 学兄弟
3. 学儿子(枚举即可)
时间复杂度为 O(n3)
#include<iostream>
#include<iomanip>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdio>
#define maxn 310
using namespace std;
int n,m,w[maxn],sz[maxn];
struct node{
int to;
node*next;
}*con[maxn];
void addedge(int x,int y)
{
node*p=new node;
p->to=y;
p->next=con[x];
con[x]=p;
}
int ls[maxn],rs[maxn],dp[maxn][maxn];
int dfs(int v)
{
sz[v]=1;
for(node*p=con[v];p;p=p->next)
{
dfs(p->to);
sz[v]+=sz[p->to];
}
if(sz[v]==1) dp[v][1]=w[v];
}
int dfs1(int v)
{
if(ls[v]!=0)
dfs1(ls[v]);
if(rs[v]!=0)
dfs1(rs[v]);
if(rs[v]!=0&&ls[v]==0)
for(int j=1;j<=m;++j) dp[v][j]=max(dp[v][j],max(dp[rs[v]][j],dp[rs[v]][j-1]+w[v]));
else
if(ls[v]!=0&&rs[v]!=0)
{
for(int j=1;j<=m;++j)
for(int k=1;k<=j;++k)
dp[v][j]=max(dp[v][j],dp[ls[v]][k-1]+w[v]+dp[rs[v]][j-k]);
for(int j=1;j<=m;++j) dp[v][j]=max(dp[v][j],max(dp[rs[v]][j],dp[rs[v]][j-1]+w[v]));
}
else
if(ls[v]!=0&&rs[v]==0)
for(int i=1;i<=sz[v];++i)
dp[v][i]=max(dp[v][i],dp[ls[v]][i-1]+w[v]);
}
int main()
{
scanf("%d%d",&n,&m);
int x,y;
for(int i=1;i<=n;++i)
scanf("%d%d",&x,&w[i]),addedge(x,i);
dfs(0);
for(int i=0;i<=n;++i)
{
if(con[i]==NULL) continue;
ls[i]=con[i]->to;
node*p=con[i],*p1=con[i]->next;
while(p1!=NULL)
{
rs[p->to]=p1->to;
p=p->next;p1=p1->next;
}
}
dfs1(0);
int ans=0;
cout<<dp[ls[0]][m];
return 0;
}