1. 程式人生 > >[Sicily 1176 Two Ends] 動態規劃 記憶化搜尋

[Sicily 1176 Two Ends] 動態規劃 記憶化搜尋

(1)問題描述

題目的意思是: 

給出一行個數為偶數的從左到右排列的卡片,每個卡片上寫有一個正整數。從玩家1開始,兩個玩家輪流從那一行卡片的最左端或是最右端取一張卡片。最後計算兩個玩家所得到的總分,總分大者為贏。玩家2使用的是貪心策略,也就是他每次都會取兩端中最大的那一個卡片。然而貪心策略並不總是最好的。現在的問題是:假設玩家1足夠聰明,在玩家2使用貪心策略的情況下,玩家1最多可以贏玩家2多少分。

(2)舉個例子

假設一個輸入樣例是這樣的:(第一個數代表卡片的個數為4,後面4個數為每個卡片的數值)

4 3 2 10 4

那麼對應的輸出應該是這樣的:

In game 1, the greedy strategy might lose by as many as 7 points.
為什麼是7 points?
玩家1選3                                卡片為2 10 4
玩家2選2和4中最大的,選4    卡片為2 10
玩家1選10                              卡片為2
玩家2選2                                遊戲結束
玩家1得分3 + 10 = 13
玩家2得分4 + 2 = 6
玩家1 - 玩家2 = 7
(3)動態規劃(Dynamic Programming)
動態規劃的本質,是對問狀態的定義狀態轉移方程的定義
a.要使用動態規劃解決這個問題,首先要思考狀態是什麼:
定義陣列dp[l][r],表示最左邊的卡片下標為為l,最右邊的卡片下標為r的時候,玩家1可以得到的最大的分數。
b.然後思考狀態轉移方程的構建:
dp[l][r]如何從前面的狀態轉移過來?
有兩種情況:
情況一:玩家1選的是左端的卡片
             如果玩家1選完之後,左端的卡片比右端的卡片大,那麼玩家2選的是左端的卡片,玩家1得分 = card[l] + dp[l - 2][r]
        如果玩家1選完之後,左端的卡片比右端的卡片小,那麼玩家2選的是右端的卡片,玩家1得分 = card[l] + dp[l + 1][r - 1]
情況二:玩家1選的是右端的卡片
              如果玩家1選完之後,左端的卡片比右端的卡片大,那麼玩家2選的是左端的卡片,玩家1得分 = card[r] + dp[l + 1][r - 1]
              如果玩家1選完之後,左端的卡片比右端的卡片小,那麼玩家2選的是右端的卡片,玩家1得分 = card[r] + dp[l][r - 2]
比較這兩種情況的得分,dp[l][r] = max(情況一得分,情況二得分);
(4)記憶化搜尋
這樣搜尋會有重複的時候,所以對於已經計算過的狀態不再計算,記憶化下來。
具體表現為初始化dp[i][j]為-1,如果後來搜尋發現已經被賦值,就直接返回。
(5)程式碼實現
#include<iostream>
using namespace std;
#define MAX 1001
int card[MAX];
int dp[MAX][MAX];
void init()
{
	fill(card, card + MAX, -1);
	for(int i = 0; i < MAX; i++)
		fill(dp[i], dp[i] + MAX, -1);
}
int max_card(int l, int r)
{
	return (card[l] > card[r] ? card[l]: card[r]);
}
int solve(int l, int r)
{
	int t1 = 0;
	int t2 = 0;
	if(r - l == 1)
		return max_card(l, r);
	if(dp[l][r] != -1)
		return dp[l][r];
	else 
	{
		if(card[l + 1] >= card[r])
			t1 = card[l] + solve(l + 2, r);
		else
			t1 = card[l] + solve(l + 1, r - 1);

		if(card[l] >= card[r - 1])
			t2 = card[r] + solve(l + 1, r - 1);
		else
			t2 = card[r] + solve(l, r - 2);
	}
	dp[l][r] = (t1 > t2 ? t1: t2);
	return dp[l][r];
}
int main()
{
	int n;
	int game_index = 1;
	while(cin >> n && n != 0)
	{
		init();
		int sum = 0;
		for(int i = 0; i < n; i++)
		{
			cin >> card[i];
			sum += card[i];
		}
		int player1 = solve(0, n - 1);
		int player2 = sum - player1;
		cout << "In game " << game_index << ", the greedy strategy might lose by as many as " << player1 - player2 << " points." << endl;
		game_index++;
	}
	return 0;
}