【動態規劃】石子合併 (ssl 2863)
阿新 • • 發佈:2018-12-15
Description
在一個操場上一排地擺放著N堆石子。現要將石子有次序地合併成一堆。規定每次只能選相鄰的2堆石子合併成新的一堆,並將新的一堆石子數記為該次合併的得分。請設計一個程式,計算出將N堆石子合併成一堆的最小得分。
Input
每組資料第1行為一個正整數N(2<=N<=100),以下N行,每行一個正整數,小於10000,分別表示第i堆石子的個數(1<=i<=N)。
Output
對於每組資料輸出一個正整數,即最小得分
Sample Input
7
13
7
8
16
21
4
18
Sample Output
239
說明:
本題之前用兩個方法做過,這次用三個方法做
先列舉前面的數(i),再列舉後面的數(j),最後列舉中間的分割線(c)(三重迴圈) 用i到c-1之間的數加上c到j的數再加上之前的得分,再將所有的值去最小。(計算一個數到另一個數可以用字首和搞定)
狀態轉移方程:
f[i][j]表示合成i到j的最小得分
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n,s[101],f[101][101],x;
int main()
{
memset(f,127/3,sizeof(f));//因為要去最小的,所以要先賦一個大的值
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&x);
s[i]=s[i-1]+x;//字首和
f[i][i]=0;//清零單個的
}
for (int i=n-1;i>=1;i--)
for (int j=i+1;j<=n;j++)
for (int c=i+1;c<=j;c++)
f[i][j]=min(f[i][j],f[i][c-1]+f[c][j]+s[j]-s[i-1]);
printf("%d",f[1][n]);
}
先列舉長度(k),再列舉前面的數(i),最後列舉分割線(c),然後後面的數(j)就可以求出來了:
之後的步驟和方法一 幾乎 完全一樣
狀態轉移方程:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n,x,j,s[101],f[101][101];
int main()
{
memset(f,127/3,sizeof(f));
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&x);
s[i]=s[i-1]+x;//字首和
f[i][i]=0;
}
for (int k=2;k<=n;k++)//複製長度
for (int i=1;i<=n-k+1;i++)
{
j=i+k-1;//求出後面的數
for (int c=i+1;c<=j;c++)
f[i][j]=min(f[i][j],f[i][c-1]+f[c][j]+s[j]-s[i-1]);
}
printf("%d",f[1][n]);
}
方法與前面的一樣,但是f[i][j]表示的是從第i個開始後面的j個數,導致了寫法不同
動態轉移方程
i+c是第二段的開頭,j-c是的二段的長度
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n,x,s[101],f[101][101];
int main()
{
memset(f,127/3,sizeof(f));
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&x);
s[i]=s[i-1]+x;//字首和
f[i][1]=0;
}
for (int j=2;j<=n;j++)//列舉長度,但表達不一樣
for (int i=1;i<=n-j+1;i++)//列舉前面的數
for (int c=1;c<j;c++)//列舉分割線
f[i][j]=min(f[i][j],f[i][c]+f[i+c][j-c]+s[i+j-1]-s[i-1]);
printf("%d",f[1][n]);
}