状态与状态转移:在最长递增子序列中的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;
}