POJ 3345 Bribing FIPA 树形dp

http://poj.org/problem?id=3345

题意 :给定N个国家,相互之间可能存在附属关系,现在想要贿赂m个国家,已知,贿赂一个国家,那么如果该国家拥有附属国,那么他的所有附属国都可以算作已经贿赂。

思路:树形dp。dp[u][j]:表示在以u为根的子树中选出j个国家的最小花费。这里在求以u为根的子树中选出j个国家的花费的时候还是用dfs 的方法,先求出孩子结点的值,然后从叶子结点递归上去,进行一次背包,求出一u为根的子树中找出j个国家的最小费用。还有很多的细节需要注意,具体见代码注释。

#include <cstdio>
#include <cstring>
#define MIN(a,b) (a)>(b)?(b):(a)
const int INF = 0x3f3f3f3f ;
char ch[210] ;
int N ,M ;
char name[210][110],na[110];
bool map[210][210] ;
int V[210], d[210];
int vote[210] ;
int dp[210][210] ;
int cnt ;

int find(char *p){
	for(int i=1;i<cnt;i++){
		if(strcmp(p , name[i]) == 0)	return i ;	
	}	
	strcpy(name[cnt] , p);
	cnt ++ ;
	return cnt - 1 ;
}

void Input(){
	int i , j ,k ,f,len,u,a ,v;
	sscanf(ch,"%d%d",&N,&M);	
	memset(map , 0 ,sizeof(map) );
	memset( d , 0 , sizeof(d) );
	cnt = 1 ;
	for(i=1;i<=N;i++){
		scanf("%s%d",ch,&a);
		u = find(ch)  ;
		V[u] = a ;
		while(getchar() != '\n'){
			scanf("%s",ch);
			v = find(ch) ;
			map[u][v] = 1 ;
			d[v] ++ ;
		}
	}
}
void dfs(int u){
	int res = 1 ;
	for(int i=1;i<=N;i++){
		if(map[u][i] == 1){
			dfs(i);	
			res += vote[i] ;
		}
	}
	vote[u] = res ;
}
void Build(int u){			//建树 
	for(int i=1;i<=N;i++){
		if(d[i])	continue ;
		map[u][i] = 1 ;	
	}	
	V[0] = INF ;
	dfs(0);	vote[0] = N + 1;
}
int DP(int u){
	dp[u][0] = 0 ;		//只有根结点一个结点,此时先假定根结点不选。 
	int nn = 0 ;		//当前根结点总共可以选择的国家数。 
	for(int v=1;v<=N;v++){
		if(map[u][v] == 0)	continue ;
		nn += DP(v) ;	
		for(int j=nn;j>=0;j--){
			for(int k=1;k<=j;k++){
				if(dp[u][j-k]<INF && dp[v][k]<INF && dp[u][j]>dp[u][j-k]+dp[v][k]){
					dp[u][j] = dp[u][j-k] + dp[v][k] ;	
				}
			}	
		}
	}	
	for(int i=1;i<=vote[u];i++){			//题目的关键:若选择了根结点,则相应的孩子全选。此处更新必须。 
		dp[u][i] = MIN( dp[u][i] , V[u]);	
	}
	return nn + 1; 			//返回以u为根的子树的国家的数目 
}
int main(){
	while(true){
		gets(ch);
		if(ch[0] == '#')	break ;
		Input() ;
		Build(0);
		memset(dp,0x3f ,sizeof(dp) );
		DP(0);
		printf("%d\n",dp[0][M]);
	}	
	return 0 ;	
}

代码:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值