洛谷p2014选课

此题的重点在于两个部分:一是如何将森林转换成一棵树,原则是添加根为0的点,将各个树链接起来;二是循环的顺序问题,因为每一种课只能选择一遍,所以只能倒序循环,有点儿想01背包

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=305;
const int maxm=305;
int n,m,cnt,a[maxn],head[maxn],dp[maxn][maxm],tot[maxn];
//f[i][k] = max(f[i][k], f[i][k - p] + f[x][p]) (1 ≤ k ≤ m , 0 ≤ p < k)
//p < k 的原因是当 p = k 的时候 f[i][k - p] 中 k - p 的值等于 0,
//这说明 i 这个节点无法被选择到,由于 i 是 x 节点的先修课,不选择 i 节点便无法选择 x 节点,
//所以应满足 p < k
struct node{
    int to,nxt;
}ed[maxn*2];

void addedge(int f,int t)
{
    ed[++cnt].to=t;
    ed[cnt].nxt=head[f];
    head[f]=cnt;
}
void dfs(int u)
{
    dp[u][1]=a[u];
    tot[u]=1;
    for(int i=head[u];i!=0;i=ed[i].nxt)
    {
        int v=ed[i].to;
        dfs(v);
        tot[u]+=tot[v];//这是优化的部分 
        //这里为什么要倒序
        //在枚举 k 的时候需要注意:由于 f[i][k] 由 f[i][k - p] 更新得到,
        //那么就需要保证更新 f[i][k] 的时候 f[i][k - p] 没有被更新,
        //由于 p 是一个正整数,所以我们倒序枚举 k 即可
        //主要问题在于子树上的课只能选一次,如果用小数去更新大数,那么子树上的课会被多选一次 
        for(int i=min(tot[u],m);i>=1;i--)
            for(int j=0;j<i;j++)
                dp[u][i]=max(dp[u][i],dp[u][i-j]+dp[v][j]);
    }
    
}
//还得将一颗树转换成森林 
int main()
{
    scanf("%d%d",&n,&m);
    int s;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&s,&a[i]);
        addedge(s,i);
    }
    //树根为0,这样就可以将森林转换为树 
    m=m+1;//因为加了一个根为0的点 
    dfs(0);
    cout<<dp[0][m];
 } 
 /**
 7  4
2  2
0  1
0  4
2  1
7  1
7  6
2  2
 **/

 

posted @ 2017-10-03 18:43  xinyimama  阅读(104)  评论(0编辑  收藏  举报