C 动态规划问题分析举例

状态与状态转移:在最长递增子序列中的dp[i]和最长公共子序列中的dp[i][j],我们通过这两个数字量的不断求解最终得到答案,这个数字量我们称为状态。首先,它是数字,抽象保存在内存。其次,它可以完全表示一个状态的特征。最后,状态的转移完全依赖状态本身。做DP题的关键是找到一个好的状态。动态规划问题的时间复杂度为状态数量和状态转移复杂度的乘积。

1.搬寝室 有n间物品,搬2*k件物品。每搬一次的疲劳度是左手 和右手物品重量差的平方成正比,每次搬两件物品。

输入 第一行有两个数 n 表示n件物品的总重量,k 。第二行有n个物品的重量
2 1
1 3 

输出 最少的疲劳度 4 

思路:物品配对情况为重量最大的和次最大的物品为一对,重量最小和次最小的物品为一对,不存在交叉组合的情况。当两个物品的重量差越小时,其差的平方也越小,所以每一对组合的两个物品,必为原物品中重量相邻的两个物品。因此,我们将物品按照重量递增排序并由1到n编号。设dp[i][j]为前j件物品选择i对物品时的最小疲劳度,若j和j-1未配对,dp[i][j]=dp[i][j-1],若配对,dp[i][j]=dp[i-1][j-2]再加上这两件物品配对产生的疲劳度。

#include<stdio.h>
#include<algorithm>
using namespace std;
#define INF 0x7fffffff  //预定义最大的int取值为无穷 
int list[2001]; //保存每个物品重量 
int dp[1001][2001];//保存状态 
int main()
{
	int n,k;
	while(scanf("%d%d",&n,&k)!=EOF)
	{
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&list[i]);
		}
		sort(list+1,list+1+n);//物品重量递增排序 
		for(int i=1;i<=n;i++)
		{
			dp[0][i]=0;
		}
		for(int i=1;i<=k;i++)
		{//递推求每个状态 
			for(int j=2*i;j<=n;j++)
			{
				if(j>2*i) dp[i][j]=dp[i][j-1];//若j>2i,最后两个物品不配对,即前j-1物品配对i对 
				else dp[i][j]=INF;//否则最后两个物品配对,先置为无穷 
				if(dp[i][j]>dp[i-1][j-2]+(list[j]-list[j-1]*list[j]-list[j]-1))//若dp[i][j]从dp[i-1][j-2]转移而来,其值优于无穷或dp[i][j-1],则更新 
				  dp[i][j]=dp[i-1][j-2]+(list[j]-list[j-1]*list[j]-list[j]-1);
			}
		}
		printf("%d\n",dp[k][n]);
	}
	return 0;
} 
2.分橘子 一堆橘子,重量从0到20000,总重量不超过2000.要求我们从中取出两堆放在扁担两头且两头重量相等。
求符合条件的每堆最大重量。没有则输出-1.
输入 
1
5
1 2 3 4 5
输出

case 1:7 

思路:设dp[i][j]表示前i个柑橘被选择后,第一堆比第二堆重j时,两堆的最大总重量和。初始时dp[0][0]=0,dp[o][j]为负无穷,两堆总重量为0.根据每一个新加入的柑橘被加入第一堆或第二堆或者不加入任一堆,设当前加入柑橘重量为list[i],这将造成第一堆与第二堆的重量差增加或减少list[i]或不变,在他们之中取最大值。当根据该状态转移方程求出所有状态后,状态dp[n][0]/2即为所求。

#include<stdio.h>
#define OFFSET 2000//柑橘重量差有负,计算重量差对应数组下标时加上该偏移值,重量差对应合法下标 
int dp[101][4001];
int list[101];
#define INF 0x7fffffff
int main()
{
	int T;
	int cas=0;
	scanf("%d",&T);
	while(T--!=0)
	{
		int n;
		scanf("%d",&n);
		bool HaveZero=false;//统计是否存在重量为0的柑橘 
		int cnt=0;//记录重量0柑橘个数 
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&list[i]);
			if(list[cnt]==0)
			{
				cnt--;
				HaveZero=true;
			}
		}
			n=cnt;
			for(int i=-2000;i<=2000;i++)
			   dp[0][i+OFFSET]=-INF;
			   dp[0][0+OFFSET]=0;
		    for(int i=1;i<=n;i++)
			   {//遍历每个柑橘 
			   	  for(int j=-2000;j<=2000;j++)
			   	  {//遍历每种可能的重量差 
			   	  	int tmp1=-INF,tmp2=-INF;//分别记录当前柑橘放在第一堆或第二堆时转移来的新值 
			   	  	if(j+list[i]<=2000&&dp[i-1][j+list[i]+OFFSET]!=-INF)
			   	  	{//当状态可由第一堆转移而来时,记录转移值 
			   	  		tmp1=dp[i-1][j+list[i]+OFFSET]+list[i];
			   	  	}
			   	  	if(j-list[i]>=-2000&&dp[i-1][j-list[i]+OFFSET]!=-INF)
			   	  	{//当状态可由第二堆转移而来时,记录转移值 
			   	  		tmp2=dp[i-1][j-list[i]+OFFSET]+list[i];
			   	  	}
			   	  	if(tmp1<tmp2)
			   	  	 tmp1=tmp2;//取两者较大的那个保存至tmp1 
			   	  	 
			   	    if(tmp1<dp[i-1][j+OFFSET])//将tmp1与当前柑橘不放人任何一堆即不发生改变的原状态比较,取最大值 
			   	  	   tmp1=dp[i-1][j+OFFSET];
			   	  	   
			   	  	dp[i][j+OFFSET]=tmp1; // 
			   	  }
			   	 
			   }
	
		printf("case %d:",++cas);
		if(dp[n][0+OFFSET]==0)
		{
			puts(HaveZero==true ? "0":"-1");
		}
		else printf("%d\n",dp[n][0+OFFSET]/2);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值