【动态规划】 石子合并问题(环形) (ssl 1597)

石 子 合 并 问 题 石子合并问题

Description

在一个圆形操场的四周摆放着n 堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。试设计一个算法,计算出将n堆石子合并成一堆的最小得分和最大得分。

编程任务:

对于给定n堆石子,编程计算合并成一堆的最小得分和最大得分。

Input

输入包括多组测试数据,每组测试数据包括两行。

第1 行是正整数n,1<=n<=100,表示有n堆石子。

第2行有n个数,分别表示每堆石子的个数。

Output

对于每组输入数据,输出两行。

第1 行中的数是最小得分;第2 行中的数是最大得分。

Sample Input

4

4 4 5 9

Sample Output

43

54

题目大意:

有n堆石子,围成一个环,可以将相邻的两堆合在一起,两堆的重量之和为你的分数,要求最大分数和最小分数

解题方法:

建议先做完石子合并(非环形)题解,再做此题,本体我们有两种方法:

方 法 一 方法一

我们先用一个二位数组f[i][j]来表示从第i对开始,后面的j个数的最小值(最大的用l),然后在后面复制一遍接下来就和石子合并差不多了

动态转移方程:

f [ i ] [ l e n ] = m i n ( f [ i ] [ l e n ] , f [ i ] [ k ] + f [ i + k ] [ l e n − k ] + s [ i + l e n − 1 ] − s [ i − 1 ] ) f[i][len]=min(f[i][len],f[i][k]+f[i+k][len-k]+s[i+len-1]-s[i-1]) f[i][len]=min(f[i][len],f[i][k]+f[i+k][lenk]+s[i+len1]s[i1])
l [ i ] [ l e n ] = m a x ( l [ i ] [ l e n ] , l [ i ] [ k ] + l [ i + k ] [ l e n − k ] + s [ i + l e n − 1 ] − s [ i − 1 ] ) l[i][len]=max(l[i][len],l[i][k]+l[i+k][len-k]+s[i+len-1]-s[i-1]) l[i][len]=max(l[i][len],l[i][k]+l[i+k][lenk]+s[i+len1]s[i1])

注释:

f[i][k]为前面,i+k是后面的开始,len-k是长度有len已经用了k,所以要减掉k,i+len-1是这一段的后面,因为第i个也要求,所以要减去i-1

#include
#include
using namespace std;
int a[205],f[205][205],l[205][205],s[205],n,minn,maxx;
int main()
{
	memset(f,127/3,sizeof(f));
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
	  {
	  	scanf("%d",&a[i]);
	  	s[i]=s[i-1]+a[i];
	  	f[i][1]=0;
	  }
	for (int i=n+1;i<=n*2;i++)
	  {
	  	s[i]=s[i-1]+a[i-n];//复制一遍
	  	f[i][1]=0;
	  }
	for (int len=2;len<=n;len++)//长度
	  for (int i=1;i<=n*2-len;i++)//前面的数,n-len+1+n-1=n*2-len
	    for (int k=1;k<len;k++)//分界线
		  {
		    f[i][len]=min(f[i][len],f[i][k]+f[i+k][len-k]+s[i+len-1]-s[i-1]);//求最小的
			l[i][len]=max(l[i][len],l[i][k]+l[i+k][len-k]+s[i+len-1]-s[i-1]);//求最大的
		  }
	minn=2147483647;
	for (int i=1;i<=n;i++)
	  {
	  	minn=min(minn,f[i][n]);//最小的
	  	maxx=max(maxx,l[i][n]);//最大的
	  }
	printf("%d\n%d",minn,maxx);
}

方 法 二 方法二

我们不在后面加一段(f和l表示的一样),直接用mod的方法,超过你的直接从1开始往后,本做法详情看动态转移方程

动态转移方程

f [ i ] [ l e n ] = m i n ( f [ i ] [ l e n ] , f [ i ] [ k ] + f [ ( i + k − 1 ) m o d n + 1 ] [ l e n − k ] + f j ( i , i + l e n − 1 ) ) ; f[i][len]=min(f[i][len],f[i][k]+f[(i+k-1)modn+1][len-k]+fj(i,i+len-1)); f[i][len]=min(f[i][len],f[i][k]+f[(i+k1)modn+1][lenk]+fj(i,i+len1));
l [ i ] [ l e n ] = m a x ( l [ i ] [ l e n ] , l [ i ] [ k ] + l [ ( i + k − 1 ) m o d n + 1 ] [ l e n − k ] + f j ( i , i + l e n − 1 ) ) ; l[i][len]=max(l[i][len],l[i][k]+l[(i+k-1)modn+1][len-k]+fj(i,i+len-1)); l[i][len]=max(l[i][len],l[i][k]+l[(i+k1)modn+1][lenk]+fj(i,i+len1));

注释:

(i+k-1) mod n+1为什么不写成(i+k)mod n呢?因为当i+k=n时,结果为0,0不在我们的计算范围内,所以我们要用(i+k-1) mod n+1,用这种的话,当i+k=n时,结果为n。fj(i,i+len-1)为计算i到i+len-1之间的数

#include
#include
using namespace std;
int a[105],f[105][105],l[105][105],s[105],n,minn,maxx;
int fj(int x,int y)
{
	if (y<=n) return s[y]-s[x-1];//没有超过n
	return fj(x,n)+fj(1,y-n);//分两段
}
int main()
{
	memset(f,127/3,sizeof(f));
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
	  {
	  	scanf("%d",&a[i]);
	  	s[i]=s[i-1]+a[i];
	  	f[i][1]=0;
	  }
	for (int len=2;len<=n;len++)//长度
	  for (int i=1;i<=n;i++)//前面的
	    for (int k=1;k<len;k++)//分界线
		  {
		    f[i][len]=min(f[i][len],f[i][k]+f[(i+k-1)%n+1][len-k]+fj(i,i+len-1));//状态转移方程
			l[i][len]=max(l[i][len],l[i][k]+l[(i+k-1)%n+1][len-k]+fj(i,i+len-1));//状态转移方程
		  }
	minn=2147483647;
	for (int i=1;i<=n;i++)
	  {
	  	minn=min(minn,f[i][n]);//求最小的
	  	maxx=max(maxx,l[i][n]);//求最大的
	  }
	printf("%d\n%d",minn,maxx);
}

你可能感兴趣的:(DP)