【树形dp】hdu 2415 Bribing FIPA

hdu 2415 Bribing FIPA

题目:
给定由若干个树组成的森林, 树上的边是有向边, 树上的每个节点都有一个代价. 若要得到某个节点, 需要付出该节点对应的代价, 若该节点拥有后继, 那么后继的节点也都能获得. 求解使用最少的代价取得至少 m 个节点

 
这道题输入是个问题,之前用 getchar()!='\n' 调了一晚上始终过不了,最后用C++的stringstream过了,还以为是dfs的问题,输入是巨坑!!!


说一下思路
1)建树的时候会遇到孤立结点,设想一个虚拟节点0,没有前驱的节点都作为虚拟根节点的后继,从而建立一个森林,这一步比较容易想到;
(2)dp[i][j]表示第i个国家及其附属国中选j个国家的最小花费;

状态转移方程为:dp[u][j+k] = min(dp[u][j], dp[u][j-k]+dp[v][k]) 需要理解!

(3)dp[u][j]的初始化问题:对于子树u使得 j~1:tot[u] 的所有dp[u][j]=v[u] ,其中tot[u]代表子树u的所有节点个数,v[u]表示节点u的费用;由于无法事先确定tot[u],只能边搜索边返回,然后取min:dp[u][j]=min{dp[u][j],v[u]},最后使用带返回值的dfs(int u)__返回子树u的所有节点数目
  最后,vector<>建树,map<>给国家编号,树形dp求解

sscanf的使用
使用stringstream对象简化类型转换

详细理解见注释:
/*Author:Hacker_vision*/
#include<bits/stdc++.h>
#define clr(k,v) memset(k,v,sizeof(k))
#define INF 0x3f3f3f3f
using namespace std;

const int _max=3e2+10;
int n,m,v[_max];//有n个国家及其附属关系,要从中选择m个国家得到m张投票;v[i]表示i的花费
char str1[110],str2[110];
int dp[310][310];//dp[u][j]=min(dp[u][j],dp[u->child][j-k]+dp[u->child][k])
bool vis[310];//判断是否有孤立结点,是的话加到虚节点
vector<int>child[_max];//vector建树
map<string,int>mp;//mp映射,给国家名编号
map<int,string>name;

int dfs(int u){
  int size=child[u].size();
  int tot=1; //存储子树i包含的所有结点数目,包括树根
  dp[u][0]=0;
  dp[u][1]=v[u];
  for(int i = 0; i < size; ++ i ){
    tot+=dfs(child[u][i]); //递归返回部分,记录子树u包含的节点数,用作初始化
    for(int j = n;j >= 0; -- j ){
       for( int k = 0; k <= j; ++ k){
        dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[child[u][i]][k]);//子节点选j-k,其他的选k个,完成选择
      }                                                    //尝试理解所有节点遍历完的情况一定是最优解
    } //注意:不一定选m个国家正合适,可能超过m反而更小,从n计数
  }
   for(int i=1; i<= tot;i++)
    dp[u][i]=min(dp[u][i],v[u]); //这部分本来应该作初始化的,但是不知道子树u的结点数,
  return tot;                    //只能边递归边初始化:若放在之前的话dp[u][i]=v[u];
}

int  main(){
 // freopen("input.txt","r",stdin);
  char str[1000];
  while( gets(str) && str[0] != '#') {
    sscanf(str,"%d%d", &n, &m);
    int top=1,cost;
    clr(vis,0);
    for(int i=0; i<=n; ++i ) child[i].clear();
    mp.clear();//使用vector\map容器先清空

    for(int i=1; i<=n; ++i ){
       scanf("%s%d",str1,&cost);
       if(!mp[str1]) mp[str1]=top++;
       v[mp[str1]]=cost;//cost赋值给对应编号的费用v[]中
         name[i]=str1;
       gets(str);
       stringstream ss(str);  //ss读入str
       while( ss >> str2 ) {  //ss读出到str2
         if(!mp[str2]) mp[str2]=top++;
         child[mp[str1]].push_back(mp[str2]);
         vis[mp[str2]]=true;
       }
    }
    v[0]=INF;
    for( int i =1;i <=n; i++ )
       if(vis[i]==false) child[0].push_back(i);//孤立节点i加入虚拟根节点,构建森林
     for( int i =0;i <=n; i++ )
        for( int j = 0; j<= n ; ++ j )
           dp[i][j]=INF; //因为要取最小值,dp[][]初始化最大即可
     dfs(0);//从虚拟节点深搜
    printf("%d\n",*min_element(dp[0]+m,dp[0]+n+1));//找到选择m~n个国家中的最小值

  }
  return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值