google kick start 2019 round C Catch Some

当当当,来更新,把本次kick start的三道题都补完了。回过头来发现,虽然没有我没有听过的算法,可是当时做的时候,确实是一个算法都没有想起来。看来,我的算法实力还是很弱,继续努力吧~

本道题考的是分组背包,但是还是不是单纯的板题,有一个坑需要通过在dp中加一维标志位来解决。废话不多说,上题。

【题意】一个人,一条街,N条狗。街用一维数组表示,人住在位置0,狗的位置可以是1,2,3,...。同一个位置可以有多条狗,每个狗有颜色。人穿带颜色的衣服,穿什么颜色的衣服就只能观察什么颜色的狗,如果想要观察和自己衣服颜色不同的狗,需要回家换衣服。人一开始在家中,每走一格耗费时间为1,观察狗和在家中换衣服不需要耗费时间。问这个人想要观察K条狗,至少需要耗费多少时间(注意,人在观察完最后一条狗时,不需要回家)。

【输入】第一行,T,代表用例个数。每个用例的第一行为N,K。N代表有多少条狗,K代表要观察多少条狗。第二行为N条狗的位置(乱序),第三行为N条狗对应的颜色(不一定从1开始)。

【输出】输出所需的最少时间

【小用例】

【input】

3
4 3
1 2 4 9
3 3 2 3
4 3
1 2 3 4
1 8 1 8
6 6
4 3 3 1 3 10000
1 2 8 9 5 7

【output】

Case #1: 8
Case #2: 6
Case #3: 10028

【解题思路】

#-------------------------------------------  我之前的暴力思路,可略过 ------------------------------------------------------------------------------------

我首先想到的暴力的思路就是使用dp[i][k]代表到以位置i结尾,观察k条狗,所需要的最短路程,我们用color[i]表示第i个的狗的颜色,pos[i]代表第i个狗的位置,状态转移为:

if color[i]==color[i-1]: dp[i][k] = min(dp[i][k], dp[j][k-1] + pos[i] - pos[j])

else: dp[i][k] = min(dp[i][k], dp[j][k-1] + pos[i] + pos[j])

但是,这样时间复杂度不够,是一个O(N^3)的算法,我们要找的是O(N^2)的算法。

我们看一下,多了哪一维,dp[i][j]是代表以位置i结尾,这就多记录了一个信息,如果代表的是前i个,这样就可以去掉一维,但是这样又不能记录颜色信息。因此,这里的i不能代表位置,要代表颜色。

以上就是我的想法,但是如果按照老的思路的话,还是一个O(N^3)的算法,我就想不下去了。在这里,我忽略了一个关键的因素。就是同一种颜色不同方案之间的不可重复性。

#  -------------------------------------------------------正解思路--------------------------------------------------------------------------------------------------

对于某一种颜色(拿红色举例)来说,选择几条狗是我们的方案,观察完w条红色狗(w=0,1,2,3,...)所需要的时间可以看做是价值。对于红色的狗,我们只能选一种方案。也就是说,我们不能先观察3条红色的狗,再回家换衣服,观察绿色的狗,然后再换衣服,观察4条红色狗,因为这样的方案,一定不如直接观察7条红色的狗时间短。

因此,就转化成了一个分组背包问题,每种颜色的狗是一组,每组中,我们选择一种方案(选择观察前几条狗,这里的前面,指的是离家的远近)。dp[i][k]代表,组合到第i种颜色,观察k条狗,最短的时间;bag[i][w]代表对于颜色i,观察w条狗的最短距离(包含回家),num[i]代表颜色为i的狗的数量。最终的输出,为dp[C][K]。看一下状态转移:

dp[i][k] = min(dp[i][k], dp[i-1][k-w] +bag[i][w]) (w:1...min(K, num[i]))

需要注意的是,人最后不用回家。我的第一想法是,按照之前做过的homework的题,通过反推(dp[i][k] == dp[i-1][k-w]+bag[i][w])来实现,然后选最大的bag[i][w]就可以了。

这样的一个问题是,我们选择的dp[C][K]是包含回家的最优方法,并不是最后不用回家的最优方案。举个例子:

1
5 3
4 1 1 1 3
1 1 3 4 1

dp[C][K] = 6,选择观察第2,3,4条狗,最后一条狗不用回家,因此,答案为5.

但是,有更优的方案。如果选择官场第1,2,5条狗的话,虽然dp[C][K]为8>6,但是最终的答案为4。

可以看出,如果仅通过上述的状态设计,我们没法求出最优的方案。

其实,我们的状态缺了是否已经选择结束的标志位。dp[i][k][0]代表到第i种颜色,观察k条狗,观察完毕的最少时间。dp[i][k][1]代表尚未观察结束。状态转移为(伪代码):

dp[i][k][1] = min(dp[i][k][1], dp[i - 1][k - w][1] + bag[i][w]);
dp[i][k][0] = min(dp[i][k][0], dp[i - 1][k - w][1] + bag[i][w] / 2,dp[i - 1][k - w][0] + bag[i][w]);
我们最终的答案为dp[C][K][0]

最后要注意的是, 颜色没有从1开始按顺序排列,所以要使用map映射一下,就ok了

【AC代码】

// ConsoleApplication3.cpp : 定义控制台应用程序的入口点。
//

#include
#include
#include
#include
#include
using namespace std;
const int maxn = 1005;
int dp[maxn][maxn][2],pos[maxn],num[maxn];
int bag[maxn][maxn];
const int inf = 1e9 + 7;
map mp;
int main()
{
	int T, N, K, color, C;
	cin >> T;
	int cnt = 1;
	while (T--)
	{
		mp.clear();
		C = 0;
		cin >> N >> K;
		memset(num, 0, sizeof(num));
		for (int i = 0; i < N; i++)
			cin >> pos[i];
		for (int i = 0; i < N; i++)
		{
			cin >> color;
			if (mp[color] == 0)
			{
				C++;
				mp[color] = C;
			}
			color = mp[color];
			num[color]++;
			bag[color][num[color]] = 2 * pos[i];
		}
		for (int i = 1; i <= C; i++)
			sort(bag[i] + 1, bag[i] + 1 + num[i]);
			
		for (int k = 0; k <= K; k++)
			dp[1][k][0] = dp[1][k][1] = inf;
		for (int w = 0; w <= num[1]; w++)
		{
			dp[1][w][1] = bag[1][w];
			dp[1][w][0] = bag[1][w] / 2;
		}
		for (int i = 2; i <= C; i++)
		{
			for (int k = 0; k <= K; k++)
			{
				dp[i][k][1]=dp[i][k][0] = inf;
				for (int w = 0; w <= min(k, num[i]); w++)
				{
					dp[i][k][1] = min(dp[i][k][1], dp[i - 1][k - w][1] + bag[i][w]);
					dp[i][k][0] = min(dp[i][k][0], dp[i - 1][k - w][1] + bag[i][w] / 2);
					dp[i][k][0] = min(dp[i][k][0], dp[i - 1][k - w][0] + bag[i][w]);
				}
					
			}
		}
		printf("Case #%d: %d\n", cnt, dp[C][K][0]);
		cnt++;
	}
	return 0;

}

 

 

你可能感兴趣的:(ACM)