POJ 1179 Polygon dp

本文介绍了解决环形表达式中通过断开一条边以获得最大得分的方法。利用枚举加动态规划的方式,针对不同运算符进行状态转移,确保在乘法运算时考虑负数相乘的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

题意:给定一个环, 求在其中断开一条边之后,最终所能得到的分数最高的所有方法。

思路:枚举+dp。给定的N的范围为1-50。因为给定的是一个环,我们只要段开其中的任意一条边,环就变成了一条线,这样我们只要枚举断开的那条边,然后求出给定的线的最高得分就可以解决该问题,在求有N个点的线的最高得分的时候,我们可以用dp[i][j]表示将i到j段消除所能得到的最高分,状态转移方程为:dp[i][j] = max{ dp[i][k-1] ope dp[k][j]   , for i<k<=j} 其中的ope视+或*而定。 但是这里还有一个问题,当运算符是*的时候,dp[i][k-1]和dp[k][j]并不一定去最大的时候才有dp[i][j]取最大,也就是说这时候不满足子问题最优化的原则,导致这个问题的原因是当两个负数相乘的时候也可以得到一个比较大的正数, 因此我们这里还需要知道[i,j]内的最小值,这样才能最终得出最大值,最终的状态转移方程为:

当运算符为‘+’时:

dp[i][j][1] = MAX( dp[i][k-1][1] +dp[k][j][1]);

dp[i][j][0] = MIN(  dp[i][k-1][0] + dp[k][j][0]);

当运算符为'*'时:

dp[i][j][0] = MIN (dp[i][k][0] * dp[k+1][j][0], dp[i][k][0] * dp[k + 1][j][1], dp[i][k][10] * dp[k + 1][j][0], dp[i][k][1] * dp[k + 1][j][1]);

dp[i][j][1] = MAX (dp[i][k][0] * dp[k+1][j][0], dp[i][k][0] * dp[k + 1][j][1], dp[i][k][10] * dp[k + 1][j][0], dp[i][k][1] * dp[k + 1][j][1]);

时间复杂度为O(N^4) ;

另外由于本题的最值可以取任意的值(包括负数),故本意最好不要用记忆化搜索。

代码:

#include<stdio.h>
#include<string.h>
#define MAX(a,b) (a)>(b)?(a):(b)
#define MIN(a,b) (a)<(b)?(a):(b)
const long long INF = 999999999999999ll ;
long long N ;
long long  val[110] ;
long long ope[110] ;
long long dp[110][110][2] ;
long long res[55],ren ;

void DP(int a, int b){
	for(int j=a;j<=b;j++){
		dp[j][j][0] = dp[j][j][1] = val[j] ;
	}
	for(int len =2 ;len<=N;len++){
		for(int i=a;i+len-1<=b;i++){
			int j = i + len -1 ;
			dp[i][j][1] = - INF ;
			dp[i][j][0] = INF ;
			for(int k=i+1;k<=j;k++){
				int aa = ope[k] ;
				if(aa == 0){
					dp[i][j][0] = MIN(dp[i][j][0] , dp[i][k-1][0] + dp[k][j][0]);
					dp[i][j][1] = MAX(dp[i][j][1] , dp[i][k-1][1] + dp[k][j][1]);	
				}	
				else{
					dp[i][j][0] = MIN(dp[i][j][0] , dp[i][k-1][0] * dp[k][j][0]);
					dp[i][j][0] = MIN(dp[i][j][0] , dp[i][k-1][1] * dp[k][j][0]);
					dp[i][j][0] = MIN(dp[i][j][0] , dp[i][k-1][0] * dp[k][j][1]);
					dp[i][j][0] = MIN(dp[i][j][0] , dp[i][k-1][1] * dp[k][j][1]);
					
					dp[i][j][1] = MAX(dp[i][j][1] , dp[i][k-1][1] * dp[k][j][1]);	
					dp[i][j][1] = MAX(dp[i][j][1] , dp[i][k-1][0] * dp[k][j][1]);
					dp[i][j][1] = MAX(dp[i][j][1] , dp[i][k-1][1] * dp[k][j][0]);
					dp[i][j][1] = MAX(dp[i][j][1] , dp[i][k-1][0] * dp[k][j][0]);
				}
			}
		}	
	}	
}
int main(){
	char ch[5] ;
	while(scanf("%lld",&N) == 1){
		for(int i=1;i<=N;i++){
			scanf("%s%lld",ch,&val[i]);
			if(ch[0]=='t'){
				ope[i] = 0 ;	
			}	
			else{
				ope[i] = 1 ;
			}
		}	
		for(int i=N+1;i<=2*N;i++){
			val[i] = val[i-N] ;
			ope[i] = ope[i-N] ;	
		}
		long long _max = -INF  ;
		ren = 0 ;
		for(int i=1;i<=N;i++){
			int s = i ,e = i + N -1 ;
			DP(s,e);
			//printf("%d %lld\n",i,dp[s][e][1]);
			if(_max < dp[s][e][1]){
				_max = dp[s][e][1] ;
				ren = 0 ;
				res[++ren] = i ;
			}
			else if(_max == dp[s][e][1]){
				res[++ren] = i ;	
			}
		}
		printf("%lld\n",_max);
		for(int i=1;i<ren;i++){
			printf("%d ",res[i]);	
		}
		printf("%d\n",res[ren]);
	}		
	return 0 ;	
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值