问题描述:
蒜头君手里有 n 张卡牌,编号从 1 到 n,每张卡牌上面有一个数字 num i 。现在蒜头君将 n 张卡牌排成一行,组成一个序列,执行以下操作:从序列中抽取一张编号为 i 的卡牌,则该张卡牌贡献的得分为 num i−1 ×num i ×num i+1 ,即卡牌上的数字同左右两张相邻的卡牌上的数字乘积。但是不能抽取序列中最左边和最右边的卡牌,即 i≠1 且 i≠n。抽到的卡牌就从序列中去掉。重复上述操作,直到序列里只剩两张卡牌。抽取的总得分为每次抽取的得分之和。 现在蒜头君想知道,怎么进行卡牌抽取,可以使得总得分最小。
输入格式:
输入有两行。第一行输入一个整数 n(3≤n≤100),表示一共有 n 张卡牌。 第二行输入 n 个整数 num i (1≤num i ≤100),表示 n 张卡牌上面的数字。
输出格式:
输出一行,输出一个整数,表示卡牌抽取的最小总得分。
样例输入:
5 20 30 5 18 3
样例输出:2520
思路:
dp[i][j]表示从将第i张到第j张牌都抽出来的最小值。若要保证dp[i][j]值不变,则需保证a[i-1]与a[j+1]值不变。假设在i到j其中最后抽出第k张牌,则抽出的价值为w[k]=a[i-1]*a[k]*a[j+1],而前面和后面区间抽出的总价值不变。所以dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]+w[k])。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,a[N],w[N],dp[N][N];
int main(){
freopen("data.in","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=2;i<=n-1;i++)
dp[i][i]=a[i-1]*a[i]*a[i+1];
for(int i=2;i<=n-2;i++)
dp[i][i+1]=min(dp[i][i]+a[i-1]*a[i+1]*a[i+2],dp[i+1][i+1]+a[i-1]*a[i]*a[i+2]);
for(int l=3;l<=n-2;l++)
for(int i=2,j;i+l-1<=n-1;i++){
j=i+l-1;
dp[i][j]=min(a[i-1]*a[i]*a[j+1]+dp[i+1][j],a[i-1]*a[j]*a[j+1]+dp[i][j-1]);
for(int k=i+1;k<=j-1;k++)
dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]+a[i-1]*a[k]*a[j+1]);
}
printf("%d",dp[2][n-1]);
return 0;
}