poj1651
题目链接:http://poj.org/problem?id=1651
题的大意是:给出一组N个数,每次从中抽出一个数(第一和最后一个不能抽),该次的得分即为抽出的数与相邻两个数的乘积。直到只剩下首尾两个数为止。问最小得分是多少?
归类:动态规划
该问题可以转化成矩阵连乘问题(当然也可以不转化),状态转移方程为:dp[i][j] = min(dp[i][k] + dp[k][j] + x[i] * x[k] * x[j])i + 1 <= k <= j - 1,小小的解释下状态转移方程的意思,dp[i][j]表示把第i个数字到第j个数字之间(不包括i,j)的数字去光后得到的最小值,假设k是i和j之间最后取出的那张卡片。dp[0][n-1]就是要求的值。
下面是代码:
#include <iostream> using namespace std; #define max 105 int dp[max][max]; int a[max]; int min(int a,int b) { return a>b?b:a; } int main() { int n; cin>>n; int i,j,k; for(i=0;i<n;i++) cin>>a[i]; for(i=0;i<n-2;i++) dp[i][i+2]=a[i]*a[i+1]*a[i+2]; int len; for(len=3;len<n;len++) for(i=0;i+len<n;i++) { j=i+len; for(k=i+1;k<j;k++) { if(dp[i][j]==0) dp[i][j]=dp[i][k]+dp[k][j]+a[i]*a[k]*a[j]; else dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+a[i]*a[j]*a[k]); } } cout<<dp[0][n-1]<<endl; return 0; }
另外我还参考了一个代码:
#include <stdio.h> #include <iostream> #include <string.h> using namespace std; int n; int a[101]; int dp[101][101]; int MIN(int x,int y) { if(x>y) return y; return x; } int main() { int i,j,len,k; while(scanf("%d",&n)!=EOF) { memset(dp,0,sizeof(dp)); for( i=1;i<=n;i++) { scanf("%d",&a[i]); } for( len=2;len<n;len++) { for( i=1;i<=n-len;i++) { j=i+len; if(len==2) dp[i][j]=a[i]*a[i+2]*a[i+1]; else for( k=i+1;k<j;k++) { if(dp[i][j]==0) dp[i][j]=dp[i][k]+dp[k][j]+a[i]*a[k]*a[j]; else dp[i][j]=MIN(dp[i][j],dp[i][k]+dp[k][j]+a[i]*a[j]*a[k]); } } } printf("%d\n",dp[1][n]); } }
我的代码是对参考代码的修改,修改后思路更加清晰。
在这里我对我的代码稍微解释下:
举个例子2,4,6,8,10
for(i=0;i<n-2;i++)
dp[i][i+2]=a[i]*a[i+1]*a[i+2];//这两句是算2,4,6;;;4,6,8;;;6,8,10这是最基本的情况,即从三个中删除一个的大小,这为后面的运算做准备
下面的更加重要:
for(len=3;len<n;len++)//len表示长度,什么长度呢?还记得前面计算的2,4,6吗,长度是a[2](大小为6)数组下标是2,a[0](大小为2,下标为0),此时长度为2,这下知道长度是什么了吧。长度为3时,则为2,4,6,8
for(i=0;i+len<n;i++)
{
j=i+len;
for(k=i+1;k<j;k++)
{
if(dp[i][j]==0)
dp[i][j]=dp[i][k]+dp[k][j]+a[i]*a[k]*a[j];
else
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+a[i]*a[j]*a[k]);//这里是重点,k是分节点,前面是一部分,后面是一部分,如2,4,6,8其中要计算(2,4,6)(6,8)两段相应的这两段前面应该已经计算了
}
}
还是感觉说不清啊,现在知道为什么很多人只贴代码而不写思路了,原来是这样啊