1. 程式人生 > >HRBUST 1818/1819 石子合併問題--直線版 /圓形版(經典動態規劃)

HRBUST 1818/1819 石子合併問題--直線版 /圓形版(經典動態規劃)

石子合併問題(直線版)

一條直線上擺放著一行共n堆的石子。現要將石子有序地合併成一堆。規定每次只能選相鄰的兩堆合併成新的一堆,並將新的一堆石子數記為該次合併的得分。請編輯計算出將n堆石子合併成一堆的最小得分和將n堆石子合併成一堆的最大得分。

Input

輸入有多組測試資料。

每組第一行為n(n<=100),表示有n堆石子,。

二行為n個用空格隔開的整數,依次表示這n堆石子的石子數量ai(0<ai<=100)

Output

每組測試資料輸出有一行。輸出將n堆石子合併成一堆的最小得分和將n堆石子合併成一堆的最大得分。 中間用空格分開。

Sample Input

3

1 2 3

Sample Output

9 11

題解:經典的區間DP題。

以求最小值為例:

狀態dp[i][j]:i到j的合併最小值。

狀態轉移方程:dp[i][j]=min(dp[i][j],dp1[i][k]+dp[k+1][j]+num[j]-num[i-1])。

num[i]表示前i個元素的和。

答案:dp[1][n]。

PS:dp[i][j]要初始化為無窮大,但是當i==j時,要初始化為0。因為當按照i,j去遍歷的話,當求dp[i][j]的時候,i=k,dp[i][k]是沒有意義的,所有要dp[i][i]=0。當求dp[i][j]的時候還有一個問題就是dp[k+1][j]可能還是沒有求出來的,所有不能按照這樣去遍歷。可以按照區間的長度len去遍歷,j=len+i-1.

具體程式碼如下:

#include<iostream>
#define INF 0x3f3f3f3f//無窮大
using namespace std;
int dp1[102][102];//最小 
int dp2[102][102];//最大 
int n;
void init()
{
	for(int i=0;i<=n;i++)
		for(int j=0;j<=n;j++)
			if(i==j)
			{
				dp1[i][j]=0;
				dp2[i][j]=0;
			}
			else
			{
				dp1[i][j]=INF;
				dp2[i][j]=0; 
			}			
}
int max(int a,int b)
{
	return a>b?a:b;
}
int min(int a,int b)
{
	return a<b?a:b;
}
int main()
{
	while(cin>>n)
	{
		init();
		int i,j,k,len;
		int num[102]={0};//前i項和 
		int a;
		for(i=1;i<=n;i++)
		{
			cin>>a;
			num[i]=num[i-1]+a;
		}
		for(len=2;len<=n;len++)
			for(i=1;i<n;i++)
			{
				j=len+i-1;
//				dp1[i][j]=INF;//前面不用init()函式的話,可以用這兩行初始化,效果一樣 
//				dp2[i][j]=0;
				if(j<=n)
					for(k=i;k<j;k++)
					{
						dp1[i][j]=min(dp1[i][j],dp1[i][k]+dp1[k+1][j]+num[j]-num[i-1]);
						dp2[i][j]=max(dp2[i][j],dp2[i][k]+dp2[k+1][j]+num[j]-num[i-1]);
					}
			}
		
		cout<<dp1[1][n]<<" "<<dp2[1][n]<<endl;
	}
	return 0;
}

石子合併問題(圓形版)

題目跟直線版就改變了一小點,就是將石子鋪在圓形軌道上,求最大值,最小值。

題解:博主的方法有點偏暴力。將直線連著鋪兩次在一條直線上,就轉化成了直線版了。算出從1到2n的dp值,再比較出所有區間長度為n的dp的最大或最小值。區間長度為n的一共為n組。

具體程式碼如下:

#include<iostream>
#define INF 0x3f3f3f3f
using namespace std;
int dp1[204][204];//最小 
int dp2[204][204];//最大 
int n;
void init()
{
	for(int i=0;i<=n*2;i++)
		for(int j=0;j<=n*2;j++)
		{
			if(i==j)
			{
				dp1[i][j]=0;		
				dp2[i][j]=0; 
			}
			else{
				dp1[i][j]=INF;			
				dp2[i][j]=0;
			}			 
		}
}
int max(int a,int b)
{
	return a>b?a:b;
}
int min(int a,int b)
{
	return a<b?a:b;
}
int main()
{
	while(cin>>n)
	{
//		init();
		int i,j,k,len;
		int num[204]={0};//前i項和 
		int a[204];
		a[0]=0;
		for(i=1;i<=n;i++)
		{
			cin>>a[i];
			num[i]=num[i-1]+a[i];
		}
		for(j=1;j<=n;j++)
		{
			num[i]=num[i-1]+a[j];
			i++;
		}
		for(len=2;len<=n*2;len++)//區間長度要變 
			for(i=1;i<n*2;i++)
			{
				j=len+i-1;
				dp1[i][j]=INF;
				dp2[i][j]=0;
				if(j<=n*2)
					for(k=i;k<j;k++)
					{
						dp1[i][j]=min(dp1[i][j],dp1[i][k]+dp1[k+1][j]+num[j]-num[i-1]);
						dp2[i][j]=max(dp2[i][j],dp2[i][k]+dp2[k+1][j]+num[j]-num[i-1]);
					}
			}
		int MAX=0,MIN=INF;
		for(i=1;i<=n;i++)
		{
			MAX=MAX>dp2[i][i+n-1]?MAX:dp2[i][i+n-1];
			MIN=MIN<dp1[i][i+n-1]?MIN:dp1[i][i+n-1];
		}
		cout<<MIN<<" "<<MAX<<endl;
	}
	return 0;
}