1. 程式人生 > >UVa Problem 10149 Yahtzee (Yahtzee 遊戲)

UVa Problem 10149 Yahtzee (Yahtzee 遊戲)

// Yahtzee (Yahtzee 遊戲)
// PC/UVa IDs: 110208/10149, Popularity: C, Success rate: average Level: 3
// 版權所有(C),邱秋,2011。metaphysis at yeah dot net
// Verdict: Accepted
// Submission Date: 2011-05-16
// UVa Run Time: 0.088s
//
// 每組色子可以選擇13種計分方式中的任意一種,已選擇的計分方式不能再次選取。如果將每組色子按每種計
// 分方式進行計分,並將分值排列如下,則可以得到一個矩陣。
// 
//       C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 C12 C13
//       ------------------------------------------
// R1  | a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13
// R2  | b1 b2 b3 b4 b5 b6 b7 b8 b9 b10 b11 b12 b13
// R3  | c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13
// R4  | d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13
// R5  | e1 e2 e3 e4 e5 e6 e7 e8 e9 e10 e11 e12 e13
// R6  | f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13
// R7  | g1 g2 g3 g4 g5 g6 g7 g8 g9 g10 g11 g12 g13
// R8  | h1 h2 h3 h4 h5 h6 h7 h8 h9 h10 h11 h12 h13
// R9  | i1 i2 i3 i4 i5 i6 i7 i8 i9 i10 i11 i12 i13
// R10 | j1 j2 j3 j4 j5 j6 j7 j8 j9 j10 j11 j12 j13
// R11 | k1 k2 k3 k4 k5 k6 k7 k8 k9 k10 k11 k12 k13
// R12 | l1 l2 l3 l4 l5 l6 l7 l8 l9 l10 l11 l12 l13
// R13 | m1 m2 m3 m4 m5 m6 m7 m8 m9 m10 m11 m12 m13
//
// 其中 Ci(i = 1,..,13),表示計分種類。Ri(i = 1,..,13),表示色子組數。a1 - a13 表示第一
// 組色子按各種計分方式所得到的分數,其餘的類推。題目的要求實際上可以轉化為下列問題(暫時不考慮獎
// 勵分的情況):從上述 13 行 13 列的矩陣中,每一行和每一列只取一個數並將取出的數相加,求出能取到的
// 最大值和取法,有多種取法可以只舉出一種。直觀的,第一行可以取 13 種取法,第二行有 12 種取法,類似
// 的,總共有 13! = 6227020800 種取法,如果用窮舉演算法,肯定是可以找到最大值的,但是程式執行
// 時間超出要求。窮舉演算法在計算過程中多次重複計算,這是導致程式執行時間大大增加的原因,為了提高效
// 率,必須減少重複計算量。有演算法基礎的人應該可以意識到應該使用動態規劃(Dynamic Programming)
// 演算法來解決該問題。
//
// Yahtzee 問題實際上就是一個典型的應用動態規劃演算法的問題。在應用動態規劃時,一個技巧是使用位掩碼
// (bitmask)來構建所有的組合情況,如上例所示,13種計分方式總共有 2^13 = 8192 種不同的組合
// 方式,對於每種組合方式求取最大值,逐步得到結果。對於上面給出的矩陣,假設第一組色子選取了第一種計
// 分方案,分值為a1,則第二組色子只能在 2 - 13 種計分方式中任選一種,怎樣表示這種狀態呢?如果把各次
// 選擇計分方式的狀態表示下列形式:
//
// C1  C2  C3  C4  C5  C6  C7  C8  C9  C10  C11  C12  C13
// 1   1   0   0   0   0   0   0   0   0    0    0    0
//
// 當某種計分方式已經使用,則該種計分方式下面為 1,否則為 0。第一組色子選擇了第一種計分方式,則 C1 為
// 1,假設第二組色子使用了第二種計分方式,則 C2 為 1,將 C1 到 C13 的 1 和 0 的狀態轉換為 1 個
// 二進位制數(C13 在最前,C1 在最末尾)則為:
//
// 0000000000011 (b)
//
// 該二進位制數為 10 進位制數的 3。若相反,第一組色子選擇第二種計分方式,第二組色子選擇第一種計分方式,這
// 樣得到的二進位制數仍然是:
//
// 0000000000011 (b)
//
// 那麼該狀態表示的是不管第一組和第二組色子選擇第一或第二種計分方式的順序如何,只要比較兩種選擇下
// 那種選擇的分值大,此時我們設立一個數組sum,將該二進位制數即十進位制的 3 作為陣列的序號,則該陣列元素
// 所表示的就是當第一和第二組色子選擇第一或第二種計分方式時的最大值。同理,對於以下二進位制數:
//
// 0000000000101 (b)
//
// 表示的是第一組色子和第三組色子各取第一或第三種計分方式,同樣以該二進位制數即十進位制下的 5 作為陣列
// 序號,將所得計分和的最大值儲存到 sum[5] 中。考慮以下二進位制數所表示的狀態:
//
// 0000000000111 (b)
//
// 前三組色子選擇了前三種計分方式,此二進位制數可能為 0000000000011 (b)與 0000000000100 (b)
// 相加而來,也可以是 0000000000101 (b) 與0000000000010 (b) 相加而來,兩種操作的含義第
// 一種是當第一組和第二組色子取遍第一種和第二種計分方式得到的最大值與第三組色子取第三種計分方式得
// 到的分值相加,第二種表示第一組和第三組色子取遍第一種和第三種計分方式所得到的最大值與第二組色子
// 取第二種計分方式得到的分值相加,如果比較兩種操作所得到的分值,並將較大值儲存到二進位制數
// 0000000000111 (b) 即十進位制數7為序數的陣列元素 sum[7] 中,則 sum[7] 的含義就是前三組色子取
// 前三種計分方式,不管選取順序如何,所得到的最大值。以此類推下去,當為下列狀態時:
//
// 1111111111111 (b)
//
// 即求得了最大值。在求最大值的過程中需要一個數組來儲存各個策略狀態最大值所採取的計分方式,以便在
// 最後根據該陣列來回溯得到各個策略狀態所採取的計分方式。對於獎勵分的處理,因為是前六項計分大於或
// 等於 63 分時會給予 35 分的獎勵分,所以在計算過程中,需將同策略的不同前六項得分的總分割槽分開來,因為大
// 於等於63的前六項得分效果是等同的,故只需考慮 0 ~ 63 這 64 種情況,一個總分,如果前六項總分大於等於
// 63,則將該總分放在陣列序號為 63 的總分元素中,小於 63 的則放在相應分數為序號的元素中,在比較
// 時,對前六項總分相同的元素比較總分大小,總分大的替代原來的元素項,那麼陣列的每一項儲存的是前六
// 項分數和等於陣列元素序號時的最大總分。如 sum[1111011100011 (b)][25] 表示的是採用策略
// 1111011100011 (b) 時前六項分數為25的最大總分,可能採用該策略的前六項分數為 25 的組合並不存在,則
// 陣列元素值設為 -1。同樣的需要在替換時記錄替換前後的所採用的計分策略和前六項得分以便回溯得到解的過
// 程。本演算法時間複雜度為 O(n * (2^n)),空間複雜度為 O(2^n)。
	
#include <iostream>
#include <sstream>
#include <algorithm>
#include <cstring>
	
using namespace std;
	
#define NDICES	5		// 每組色子的數量。
#define NROUNDS	13		// 擲色子的輪數。
#define NCOMBINATIONS	8192	// 1 << 13,計分種類的不同組合方式種數。
#define NCATEGORIES	13	// 計分種類。
#define NUPPER	64		// 63(獎勵分條件) + 1
#define NBOUNS	35		// 獎勵分
	
// 為計算一個二進位制數中 1 的位數定義的巨集。
#define POW(c) (1<<(c))
#define MASK(c) (((unsigned long)-1) / (POW(POW(c)) + 1))
#define ROUND(n, c) (((n) & MASK(c)) + ((n) >> POW(c) & MASK(c)))
	
// 計算整數 n 表示為二進位制數時位為 1 的個數。
int bits(int n)
{
	n = ROUND(n, 0);
	n = ROUND(n, 1);
	n = ROUND(n, 2);
	n = ROUND(n, 3);
	n = ROUND(n, 4);

	return n;
}
	
// 根據不同計分方式計算一組色子的分值,對於後6種計分方式,需要先判斷
// 是否符合該種計分方式的要求,符合則返回相應的分數,否則計 0 分。
int scoring(int dices[NDICES], int category)
{
	// 計一到計六。
	int ret = 0;
	if (category < 6)
	{
		for (int i = 0; i < NDICES; i++)
			if (dices[i] == (category + 1))
				ret += dices[i];
	}
	else
	{
		switch (category)
		{
				// 機會。
			case 6:
				for (int i = 0; i < NDICES; i++)
					ret += dices[i];
				break;
				// 三同。
			case 7:
				if (dices[0] == dices[2] || dices[1] == dices[3]
				 || dices[2] == dices[4])
					for (int i = 0; i < NDICES; i++)
						ret += dices[i];
				break;
				// 四同。
			case 8:
				if (dices[0] == dices[3] || dices[1] == dices[4])
					for (int i = 0; i < NDICES; i++)
						ret += dices[i];
				break;
				// 五同。
			case 9:
				if (dices[0] == dices[4])
					ret = 50;
				break;
				// 小順。
			case 10:
				bool value[6];
				memset(value, 0, sizeof(value));

				for (int i = 0; i < 5; i++)
					value[dices[i] - 1] = true;

				for (int i = 0; i < 3; i++)
					if (value[i] && value[i + 1] && 
					value[i + 2] && value[i + 3])
						ret = 25;
				break;
				// 大順。
			case 11:
				if (dices[1] == (dices[0] + 1) &&
					dices[2] == (dices[1] + 1) &&
					dices[3] == (dices[2] + 1) &&
					dices[4] == (dices[3] + 1))
					
					ret = 35;
				break;
				// 葫蘆。
			case 12:
				if (dices[0] == dices[1] &&
					dices[2] == dices[4] ||
					dices[0] == dices[2] &&
					dices[3] == dices[4])
					ret = 40;
				break;
		}
	}
	
	return ret;
}
	
void dynamic_programming(int yahtzee[NROUNDS][NDICES])
{
	int score[NROUNDS][NROUNDS];	// 儲存各組色子按不同計分方式所得分。
	int sum[NCOMBINATIONS][NUPPER];	// 儲存每種策略的總分數。
	// memo[NCOMBINATIONS][NUPPER][0] 記錄每種策略所使用的
	// 計分方式,memo[NCOMBINATIONS][NUPPER][1] 記錄前六項分數。
	int memo[NCOMBINATIONS][NUPPER][2];
	
	// 計算第(i + 1)組色子使用第(j + 1)種計分方式時的得分。
	for (int i = 0; i < NROUNDS; i++)
		for (int j = 0; j < NCATEGORIES; j++)
			score[i][j] = scoring(yahtzee[i], j);

	
	// 初始化總分陣列為 -1,未選擇策略時總分為 0。
	memset(sum, -1, sizeof(sum));
	sum[0][0] = 0;
	
	int b, s, t, d, a;
	// 遍歷所有可能的計分組合方式,並計算每種組合方式下的最大分值,
	for (int m = 0; m < NCOMBINATIONS; m++)
		for (int c = 0; c < NCATEGORIES; c++)
			// 必須保證第(c + 1)種計分方式尚未使用。
			if (!(m & (1 << c)))
			{
				// 計算當m表示為二進位制數時位為1的個數,表示
				// 當前是為第(b + 1)組色子選擇計分方式。
				b = bits(m);

				// 第(b + 1)組色子的第(c + 1)種計分方式得分。
				s = score[b][c];

				// 當前使用策略的二進位制標誌。
				t = m | (1 << c);

				// 如果所選計分方式為前六種方式之一,則加上該組色子此種計分方式得分。
				a = (c < 6) ? s : 0;	
				for (int u = 0; u < NUPPER; u++)
					if (sum[m][u] > -1)
					{
						d = ((u + a) < (NUPPER - 1) ? 
							(u + a) : (NUPPER - 1));

						if (sum[t][d] < (sum[m][u] + s))
						{
							memo[t][d][0] = c;
							memo[t][d][1] = u;
							sum[t][d] = sum[m][u] + s;
						}
					}
			}
	
	// 判斷最大總分是否包含獎勵分。
	int max = 0, bouns = 0, upper, total;
	// 無獎勵分的最大總分值。
	for (int u = 0; u < NUPPER - 1; u++)
		if (sum[NCOMBINATIONS - 1][u] > max)
		{
			max = sum[NCOMBINATIONS - 1][u];
			upper = u;
		}
	
	// 有獎勵分的最大總分值。
	total = max;
	if (sum[NCOMBINATIONS - 1][NUPPER - 1] > -1)
	{
		bouns = NBOUNS;
		total = sum[NCOMBINATIONS - 1][NUPPER - 1] + bouns;
	}
	
	// 比較兩種總分值的大小。
	if (max < total)
	{
		max = total;
		upper = NUPPER - 1;
	}
	
	// 根據 memo 陣列回溯得到解的過程。
	int last = NCOMBINATIONS - 1;
	int category[NROUNDS];
	for (int i = NROUNDS - 1; i >= 0; i--)
	{
		category[i] = memo[last][upper][0];
		upper = memo[last][upper][1];
		last ^= (1 << category[i]);
	}
	
	// 根據解輸出結果。
	for (int i = 0; i < NCATEGORIES; i++)
		for (int j = 0; j < NROUNDS; j++)
			if (category[j] == i)
				cout << score[j][i] << " ";
	
	cout << bouns << " " << max << endl;
}
	
bool cmp(int a, int b)
{
	return a < b;
}
	
int main(int ac, char *av[])
{
	int yahtzee[NROUNDS][NDICES];
	string line;
	int count = 0;
	
	while (getline(cin, line))
	{
		istringstream iss(line);
		for (int i = 0; i < NDICES; i++)
			iss >> yahtzee[count % NROUNDS][i];

		sort(yahtzee[count % NROUNDS], yahtzee[count % NROUNDS] + NDICES, cmp);

		if (++count % NROUNDS == 0)
			dynamic_programming(yahtzee);
	}
	
	return 0;
}


相關推薦

UVa Problem 10149 Yahtzee Yahtzee 遊戲

// Yahtzee (Yahtzee 遊戲) // PC/UVa IDs: 110208/10149, Popularity: C, Success rate: average Level: 3 // 版權所有(C),邱秋,2011。metaphysis at yeah

UVA 221 城市化地圖離散化

span uva 部分 精度 spa 最大的 分析 重疊 pan 題意: 分析: 記錄一個一開始就想錯的觀點, 以為只要把x 和 width放大到到足夠大(例如10000倍,倍數越高精度越高),然後排序填充一下數軸就可以,就可以解決x坐標是小數的問題。但這樣打了一下,發

UVa 12716 GCD XOR 簡單證明

i++ map esp typedef -a type print const max 題意: 問 gcd(i,j) = i ^ j 的對數(j <=i <= N ) N的範圍為30000000,有10000組例子 思路:GCD(a,b) = a^b =

LeetCode 55. Jump Game 跳躍遊戲

mat col lean osi pub 情況 you track rip Given an array of non-negative integers, you are initially positioned at the first index of the ar

UVa】Biggest Numberdfs+剪枝

scanf sin ret break puts 從大到小 如果 ssl ges 題目 題目 ? ? 分析 典型搜索,考慮剪枝。 統計一下聯通分量。 1、本位置能夠達到所有的點的數量加上本已有的點,還沒有之前的結果長,直接返回。 2、當本位置能夠達到所有的點的數量加上本

UVa-1225 Digit Counting數數字

c++代碼 body class tor count pac cout auto pre 對於一個大於1且小於10000的整數N,我們定義一個唯一與之相關聯的序列。例如,若N為13,則該序列為12345678910111213。現要求對於一個輸入值N,記錄這個序列中每個數字

bzoj3212: Pku3468 A Simple Problem with Integers線段樹

while printf scan font geo main post align sim 3212: Pku3468 A Simple Problem with Integers 題目:傳送門 題解:    感謝Rose_max大佬的傾情相

uva 1590 - IP NetworksIP地址

32位 http sub sample com lock 就是 subst top 習題4-5 IP網絡(IP Networks, ACM/ICPC NEERC 2005, UVa1590) 可以用一個網絡地址和一個子網掩碼描述一個子網(即連續的IP地址範圍)。其中子網

UVa 580 - Critical Mass遞推

ext blank href lan page show break tar [] 鏈接: https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_pro

UVA - 12487 Midnight CowboyLCA+思維

for com ase fir 分析 cowboy cond memset -h https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&

UVA-1220-Party at Hali-Bula && UVA-1218-Perfect Service樹形DP

UVA-1220-Party at Hali-Bula 題意: 一個公司員工要舉行聚會,要求任意一個人不能和他的直接上司同時到場,一個員工只有一個支系上司,現在求最多有多少人到場,並且方案是否唯一(紫書282頁) 分析: 紫薯寫的很清楚,而且也很基礎,就不重複了,只做幾點記錄和總結 輸入中輸入

CodeForces - 276E Little Girl and Problem on Trees線段樹

題意 現在給你一棵這樣的樹,除了根節點外,其他的所有節點度都為2,也就是說除了根節點之外其他的節點都只能有一個節點,現在有兩種操作: 1。以當前節點為中心,距離為d的範圍內的點全部都加上x。 2。查詢某個點的值 思路* 觀察這樣一張圖,你會發現他們其實都是一條一條的鏈狀的,所以對於更新

UVa 11538 Chess Queen計數問題

題目傳送門     題意:給你一個n*m(n行m列)的棋盤,棋盤裡有兩個黑白旗子,當黑白旗子在同一行,同一列,同一對角線時處於攻擊狀態,問處於攻擊狀態時的種類數。 題解:根據題意可以將種類數分為三種類型: 一、當兩種棋子處於同一列的情況:ans1=m*(

利用微信小程式遊戲API製作適配cocos creator小遊戲排行榜的例項程式

cocos creator 可以通過新建一個creator專案進行新增子域專案,但是有一個缺點就是佔用檔案大小是一個問題,所以我這裡採用微信的API進行繪製排行榜, 主域就是各種傳送給子域的訊息,這裡不再這裡贅述,就是各種呼叫微信的API 這裡給出微信的API 微信開放資料域 新建m

【HDOJ5996】dingyeye loves stoneNim遊戲

題意:dingyeye喜歡和你玩石子游戲。dingyeye有一棵n個節點的有根樹,節點編號為0到n−1,根為0號節點。 遊戲開始時,第i個節點上有a[i]個石子。兩位玩家輪流操作,每次操作玩家可以選擇一個節點,並將該節點上的一些石子(個數不能為0)移動到它的父親節點上去。 如果輪到某位玩家時,該玩家沒有任

UVA 796-Critical Links 求橋

In a computer network a link L, which interconnects two servers, is considered critical if there are at least two servers A and B such that all networ

POJ 3468 A Simple Problem with Integers線段樹

truct fin clas define class 基本 open urn display 題目鏈接:A Simple Problem with Integers 題意:N個數字,M次操作;操作分為兩種,第一種將$[L,R]$區間內的每個數都加上C,第二種為求$[L,

UVA - 10648 Chocolate Box 概率dp

題目大意:       n個巧克力,放在m個盒子裡,問至少有一個盒子是空的概率 題解:      dp[i][j]表示i個巧克力放到j個盒子裡的概率      dp[i][j

A + B Problem HDU - 1000語法練習題

Calculate A + B. Input Each line will contain two integers A and B. Process to end of file. Output For each case, output

Problem E – Enigma數位dp

題意: 給一個1000位大數,有些位置用?遮住,給出這個大數的一個1000以內的因子k,求這個大數(多個輸出最小的,沒有輸出*) 解析: 這麼經典的數位dp居然沒想出來。 dp[i][j]表示到第i位為止可能%k=j的第i位上的最小數設第i位上的數為x,r為i