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)两段相应的这两段前面应该已经计算了
}
}

还是感觉说不清啊,现在知道为什么很多人只贴代码而不写思路了,原来是这样啊

posted on 2012-06-26 01:25  矮人狙击手!  阅读(2214)  评论(0编辑  收藏  举报

导航