题目链接http://poj.org/problem?id=1651
Time Limit: 1000MS | Memory Limit: 65536K | |
Total Submissions: 8569 | Accepted: 5353 |
Description
The goal is to take cards in such order as to minimize the total number of scored points.
For example, if cards in the row contain numbers 10 1 50 20 5, player might take a card with 1, then 20 and 50, scoring
If he would take the cards in the opposite order, i.e. 50, then 20, then 1, the score would be
Input
Output
Sample Input
6 10 1 50 50 20 5
Sample Output
3650
递推式是 DP 的核心,得到递推式也就基本得到了 DP 的代码。DP 问题的特征是最优子结构,即问题包含多个子问题,问题的最优解中包含子问题的最优解。求解过程中相同的子问题可能多次被遇到,把已经求出答案的子问题的解记录到表中,这样问题的复杂度通常从指数降低到多项式。
(1)规模最小的子问题最容易,是三个数字,结果是三个数字的乘积。
(2)为了把问题分解成规模更小的子问题,定义问题如下,给出一个整数数组 c [ ],求索引从 i 到 j 范围的一串卡片操作到最后得到的最小结果是 s[i][j],问题规模是卡片数,即 j - i - 1;
(3)类似矩阵乘法,现在把 s[i][j] 从中间某个位置(i < k < j)分裂成规模更小的两个子问题,即是把数字串的长度减小:
c[i], ..., c[k], ..., c[j]; ( c[k] 是最后取走的牌 )
先把 c[i] , ... , c[k] 中间的所有数字取走,根据问题定义得到结果是 s[i][k]; 剩下的是 c[i], c[k], ..., c[j];
再把 c[k], ... , c[j] 中间的所有数字取走,根据问题定义得到结果是 s[k][j]; 剩下的是 c[i], c[k], c[j];
最后取走 c[k];因此如果最后取走 k ,则 s[i][j] = s[i][k] + s[k][j] + c[i]*c[k]*c[j];
因此这就是要找的递推式:(由于题目要求得到的结果最小,因此下面的式中取最小值)
s[i][j] = min ( s[i][k] + s[k][j] + c[i]*c[k]*c[j] ); ( i < k < j )
从上式可以看到,子问题的最优解是最终问题的最优解的一部分。
解法和矩阵乘法相同,都是从下至上求解(子问题规模从小到大),即先求解最小的问题,逐渐叠加上去,直到得到最终的规模问题的解。在此题中问题规模是数字串包含的数字个数,从 3 一直增加到卡片总数。在求解问题规模为 j - i + 1 的所有问题时,依赖更小规模的子问题,也就是说所有长度小于 j - i + 1的子问题都应该已求解完毕。从矩阵 s 来看,是从近对角线位置依次求解到矩阵右上角。
#include <iostream>
#include <string.h>
using namespace std;
int dp[105][105];
int main()
{
int n, a[105];
while(cin >> n){
for(int i = 1; i <= n; i++){
cin >> a[i];
dp[i-1][i] = 0;
}
// memset(dp, 0, sizeof(dp));//这里其实是把dp[i][i+1]置为0,所以直接将其放到上面for循环里也可以
for(int len = 2; len < n; len++)
for(int i = 1; i < n - len + 1; i++){
int j = i + len;
dp[i][j] = 0x3f3f3f3f;
for(int k = i + 1; k < j; k++)
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + a[i]*a[k]*a[j]);
}
cout << dp[1][n] << endl;
}
return 0;
}