算法竞赛入门经典 例题9-8

UVa 01625

Color Length

某个城市因为修路,需要将上下两车道关闭为上下单车道,即每个方向需要将两车道的车流合并到一条车道中。每辆车的颜色用大写字母表示,要求合并后的车流中每种颜色车的最大距离之和最小,例如合并后的车流为GBYBRYRGB,则距离分别为L(G) = 7L(B) = 7L(Y) = 3L(R) = 2,和为19

每一步要做的决策就是从第1条车道,或者第2条车道中选择一辆车汇入到新的车流中。假设已经从第1条车道汇入了i辆车且从第2条车道汇入了j辆车,现在需要判断是选取第i辆车还是第j辆车,然后计算新的最小值。如果新的车是该颜色的最后一辆车,那么要往前去找该颜色的第1辆车,也就是要把最多26种颜色的车的首位置都记录下来,这样状态空间会有28维,显然这种方法是无后效的,但更显然的是这个方法不可行。

上面的问题在于只根据这辆车的颜色计算了该颜色的L值,而没有考虑到对其它颜色L值的影响。如果可以考虑所有颜色的L值,也就是考虑了所有L值的和:

  • 如果新的车是该颜色的最后一辆车,那么不仅可以计算此颜色的L值,也可以将其余未结束的颜色的L值都递增,更一般的,就是将所有未结束的颜色的L值递增;
  • 如果新的车不是该颜色的最后一辆车,那么需要将其余未结束颜色的L值都递增,同时根据该颜色是否出现过对该颜色进行递增,更一般的,就是将所有未结束的颜色的L值递增;
  • 因此只需递增所有未结束颜色的L值;

这种方法不需要知道前面i + j辆车中每种颜色的车的起始位置,只需要知道每种颜色是否结束就可以了,而颜色是否结束只和(i, j)有关系,保证了最优子结构和无后效性。

这样问题就变成了计算Left(i, j),表示在从两条车道分别选取i辆车和j辆车时,在0 ~ i0 ~ j中至少出现过一次且在i + 1 ~ nj + 1 ~ m中还会出现的颜色的数量。在二重循环内对颜色进行枚举,根据每种颜色的起始位置和结束位置即可计算Left。这里0表示的是0辆车,如果0表示的是下标,则Left(0, 0)就直接表示了两辆车的情况,没有车和只有一辆车的情况需要单独写,感觉更麻烦一些。

有了Left,就可以计算最小值了,递推公式为Length[i][j] = min(Length[i - 1][j] + Left[i - 1][j], Length[i][j - 1] + Left[i][j - 1])。如果新的颜色是第2个至最后一个,则新的颜色也需要递增,Left[i - 1][j]也包括新的颜色;如果新的颜色是第1个(或者同时也是最后1个),则不需要递增新的颜色,Left[i - 1][j]不包括新的颜色。

但是这样写起来有问题,因为0表示选择了0辆车,再减1就变成了负数了,因此代码写成了向上递推的形式。

#include <iostream>
#include <string>
#include <vector>
#include <climits>

#define A_2_O(c) ((c) - 'A')

using namespace std;

void calPosition(const string &str1, const string &str2, vector<pair<size_t, size_t>> &FstPos, vector<pair<size_t, size_t>> &LstPos)
{
	size_t i = 0, j = 0;
	while (i < str1.size() && j < str2.size()) {
		LstPos[A_2_O(str1[i++])].first = i;
		LstPos[A_2_O(str2[j++])].second = j;
	}
	while (i < str1.size()) {
		LstPos[A_2_O(str1[i++])].first = i;
	}
	while (j < str2.size()) {
		LstPos[A_2_O(str2[j++])].second = j;
	}

	while (i > 0 && j > 0) {
		FstPos[A_2_O(str1[--i])].first = i + 1;
		FstPos[A_2_O(str2[--j])].second = j + 1;
	}
	while (i > 0) {
		FstPos[A_2_O(str1[--i])].first = i + 1;
	}
	while (j > 0) {
		FstPos[A_2_O(str2[--j])].second = j + 1;
	}
}

void findMinimumColorLength(const string &str1, const string &str2)
{
	vector<vector<int>> Length(str1.size() + 2, vector<int>(str2.size() + 2, INT_MAX));
	vector<vector<int>> Left(str1.size() + 1, vector<int>(str2.size() + 1, 0));
	vector<pair<size_t, size_t>> FstPos(26, pair<size_t, size_t>(UINT_MAX, UINT_MAX));
	vector<pair<size_t, size_t>> LstPos(26, pair<size_t, size_t>(UINT_MAX, UINT_MAX));

	calPosition(str1, str2, FstPos, LstPos);

	Left[0][0] = 0;
	for (size_t i = 0; i <= str1.size(); i++)
	{
		for (size_t j = 0; j <= str2.size(); j++)
		{
			for (char c = A_2_O('A'); c <= A_2_O('Z'); c++)
			{
				if ((
					(FstPos[c].first != UINT_MAX && FstPos[c].first <= i)
					|| (FstPos[c].second != UINT_MAX && FstPos[c].second <= j)
					) && (
					(LstPos[c].first != UINT_MAX && LstPos[c].first > i)
						|| (LstPos[c].second != UINT_MAX && LstPos[c].second > j)
						)) {
					Left[i][j]++;
				}
			}
		}
	}

	Length[0][0] = 0;
	for (size_t i = 0; i <= str1.size(); i++)
	{
		for (size_t j = 0; j <= str2.size(); j++)
		{
			if (Length[i + 1][j] > Length[i][j] + Left[i][j]) {
				Length[i + 1][j] = Length[i][j] + Left[i][j];
			}
			if (Length[i][j + 1] > Length[i][j] + Left[i][j]) {
				Length[i][j + 1] = Length[i][j] + Left[i][j];
			}
		}
	}

	cout << Length[str1.size()][str2.size()] << endl;
}

int main()
{
	int T;
	cin >> T;
	for (int t = 0; t < T; t++)
	{
		string str1, str2;
		cin >> str1 >> str2;
		findMinimumColorLength(str1, str2);
	}
	return 0;
}
/*
2
AAABBCY
ABBBCDEEY
GBBY
YRRGB
*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值