动态规划——石子合并问题

石子合并问题分三类:

1、任意合并
2、相邻的才能合并且排成直线
3、相邻的才能合并且围成圈

1、贪心即可解决

合并果子(贪心题)

2、直线型

合并石子直线型
对于区间dp问题,我们先要把它细分为区间
我们可以这样定义dp数组
dp[i][j]的意义是,合并第i个石子到第j个石子的最小(最大)得分
那就开始找吧

int findMin(int l, int r)
{
	if(dp[l][r])
	return dp[l][r];
	if(l == r)
	return 0;
	int _min = MAX;//0x3f3f3f3f
	int s = 0;
	for(int i = l; i <= r; i++)
	s += a[i];//区间总和,最后一下合并 
	for(int i = l; i < r; i++)
	_min = min(_min, findMin(l, i) + findMin(i+1, r) + s);//将区间细分
	dp[l][r] = _min;
	return dp[l][r];
}

s的意义是找到l到r区间内所有石子的分之和,用于把最后两大堆合起来
AC代码:

#include
#include
#include
using namespace std;
int n;
int dp[107][107];
int a[107];
const int MAX = 0x3f3f3f3f;

int findMin(int l, int r)
{
	if(dp[l][r])
	return dp[l][r];
	if(l == r)
	return 0;
	int _min = MAX;
	int s = 0;
	for(int i = l; i <= r; i++)
	s += a[i];//区间总和,最后一下合并 
	for(int i = l; i < r; i++)
	_min = min(_min, findMin(l, i) + findMin(i+1, r) + s);
	dp[l][r] = _min;
	return dp[l][r];
}

int findMax(int l, int r)
{
	if(dp[l][r])
	return dp[l][r];
	if(l == r)
	return 0;
	int _max = -1;
	int s = 0; 
	for(int i = l; i <= r; i++)
	s += a[i];//区间总和,最后一下合并 
	for(int i = l; i < r; i++)
	_max = max(_max, findMax(l, i) + findMax(i+1, r) + s);
	dp[l][r] = _max;
	return dp[l][r];
}
int main()
{
	while(scanf("%d", &n) != EOF)
	{
		memset(dp,0,sizeof(dp));
		for(int i = 1; i <= n; i++)
			cin >> a[i];
		cout << findMin(1,n) << " ";
		memset(dp,0,sizeof(dp));
		cout <<findMax(1,n) << endl;
	}
}

注:思路来源于集训队冷包学长,谢谢学长

3、围成圈

合并石子环形
这时我们dp数组的意义改变了
dp[i][j]表示从第i个元素开始往后走j个元素的最小(最大)值
因为如果五个元素的话
dp[1][5]
dp[2][5]

dp[5][5]
这几个的值大概率是不一样的

如何解决圈的问题呢?

思路一:

强行给他围成圈
运用取模运算的方法

for(int i = 2; i <= n; i++)//走几步 
		{
			for(int j = 1; j <= n; j++)//从哪开始 
			{
				dp_max[j][i] = -1;
				dp_min[j][i] = MAX;
				for(int k = 1; k < i; k++)
				{
					dp_max[j][i] = max(dp_max[j][i], dp_max[j][k] + dp_max[(j+k-1) % n + 1][i - k] + cost(j, i));
					dp_min[j][i] = min(dp_min[j][i], dp_min[j][k] + dp_min[(j+k-1) % n + 1][i - k] + cost(j, i));
				}
			}
		}

(j+k-1)%n+1 从1到n又马上接上1,完美地避开了0

思路二:

开两倍数组,将元素按原来的顺序复制在后面
比如
样例 :
3
1 2 3
我们这样存数据
1 2 3 1 2 3
这样就围成了圈,是不是很棒?

for(int i = 1; i <= n; i++)
		{
			scanf("%d", &a[i]);
			a[i + n] = a[i];//在这里实现
			dp_min[i][i] = 0;
			dp_min[i + n][i + n] = 0; 
		}

然后应该不太难了
这里是dp部分:

for(int len = 2; len <= n; len++)//走几步 
		{
			for(int i = 1; len + i - 1 <= 2 * n; i++)//从哪开始 
			{
				int j = len + i - 1;
				for(int k = i; k < j; k++)
				{
					dp_min[i][j] = min(dp_min[i][j], dp_min[i][k] + dp_min[k + 1][j] + cost[j] - cost[i - 1]);
					dp_max[i][j] = max(dp_max[i][j], dp_max[i][k] + dp_max[k + 1][j] + cost[j] - cost[i - 1]);
				}
			}
		}

然后AC啦:

#include
#include
#include
using namespace std;
int n;
int dp_max[207][207];
int dp_min[207][207];
int cost[207];
int a[203];
const int MAX = 0x3f3f3f3f;

int main()
{
	while(scanf("%d", &n) != EOF)
	{
		memset(dp_min, MAX, sizeof(dp_min));
		memset(dp_max, 0, sizeof(dp_max));
		for(int i = 1; i <= n; i++)
		{
			scanf("%d", &a[i]);
			a[i + n] = a[i];
			dp_min[i][i] = 0;
			dp_min[i + n][i + n] = 0; 
		}
		cost[0] = 0;
		
		for(int i = 1; i <= 2 * n; i++)//为了后面方便用前缀和
		cost[i] = cost[i - 1] + a[i];
		
		for(int len = 2; len <= n; len++)//走几步 
		{
			for(int i = 1; len + i - 1 <= 2 * n; i++)//从哪开始 
			{
				int j = len + i - 1;
				for(int k = i; k < j; k++)
				{
					dp_min[i][j] = min(dp_min[i][j], dp_min[i][k] + dp_min[k + 1][j] + cost[j] - cost[i - 1]);
					dp_max[i][j] = max(dp_max[i][j], dp_max[i][k] + dp_max[k + 1][j] + cost[j] - cost[i - 1]);
				}
			}
		}
		int ans_min = MAX;
		int ans_max = -1;
		for(int i = 1; i <= n; i++)
		{
			ans_min = min(ans_min, dp_min[i][i+n-1]);
			ans_max = max(ans_max, dp_max[i][i+n-1]);
		}
		cout << ans_min << " " << ans_max << endl;
	}
	return 0;
}

你可能感兴趣的:(动态规划——石子合并问题)