1. 程式人生 > >【動態規劃】石子合併 (ssl 2863)

【動態規劃】石子合併 (ssl 2863)

石子合併

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 ] = m i n ( f [ i ] [ j ] , f [ i ] [ c 1 ] + f [ c ] [ j ] + s [ j ] s [ i 1 ] ) f[i][j]=min(f[i][j],f[i][c-1]+f[c][j]+s[j]-s[i-1])

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)就可以求出來了:

j = i + k 1 j=i+k-1

之後的步驟和方法一 幾乎 完全一樣

狀態轉移方程:

f [ i ] [ j ] = m i n ( f [ i ] [ j ] , f [ i ] [ c 1 ] + f [ c ] [ j ] + s [ j ] s [ i 1 ] ) f[i][j]=min(f[i][j],f[i][c-1]+f[c][j]+s[j]-s[i-1])

#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個數,導致了寫法不同

動態轉移方程

f [ i ] [ j ] = m i n ( f [ i ] [ j ] , f [ i ] [ c ] + f [ i + c ] [ j c ] + s [ i + j 1 ] s [ i 1 ] ) f[i][j]=min(f[i][j],f[i][c]+f[i+c][j-c]+s[i+j-1]-s[i-1])

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]);
}