1. 程式人生 > >1089 狼人殺-簡單版 (20 分)

1089 狼人殺-簡單版 (20 分)

1089 狼人殺-簡單版 (20 分)

PS. 想了很久,思路始終停留在誰說謊了,最終按照這個思路寫出了程式碼之後,朋友提醒 這道題不需要證明誰說謊了,而是證誰是狼人的時候 我懵逼。開啟百度一搜 發現十多行的題解!!哎呀,自愧不如ing

以下文字摘自《靈機一動·好玩的數學》:“狼人殺”遊戲分為狼人、好人兩大陣營。在一局“狼人殺”遊戲中,1 號玩家說:“2 號是狼人”,2 號玩家說:“3 號是好人”,3 號玩家說:“4 號是狼人”,4 號玩家說:“5 號是好人”,5 號玩家說:“4 號是好人”。已知這 5 名玩家中有 2 人扮演狼人角色,有 2 人說的不是實話,有狼人撒謊但並不是所有狼人都在撒謊。扮演狼人角色的是哪兩號玩家?

本題是這個問題的升級版:已知 N 名玩家中有 2 人扮演狼人角色,有 2 人說的不是實話,有狼人撒謊但並不是所有狼人都在撒謊。要求你找出扮演狼人角色的是哪幾號玩家?

輸入格式:

輸入在第一行中給出一個正整數 N(5≤N≤100)。隨後 N 行,第 i 行給出第 i 號玩家說的話(1≤i≤N),即一個玩家編號,用正號表示好人,負號表示狼人。

輸出格式:

如果有解,在一行中按遞增順序輸出 2 個狼人的編號,其間以空格分隔,行首尾不得有多餘空格。如果解不唯一,則輸出最小序列解 —— 即對於兩個序列 A=a[1],...,a[M] 和 B=b[1],...,b[M],若存在 0≤k<M 使得 a[i]=b[i] (i≤k),且 a[k+1]<b[k+1],則稱序列 A 小於序列 B。若無解則輸出 No Solution

輸入樣例 1:

5
-2
+3
-4
+5
+4

輸出樣例 1:

1 4

輸入樣例 2:

6
+6
+3
+1
-5
-2
+4

輸出樣例 2(解不唯一):

1 5

輸入樣例 3:

5
-2
-3
-4
-5
-1

輸出樣例 3:

No Solution

我就講我的具體的思路:

1.看了一下時間 400ms 決定暴力列舉狼。

2.注意到題目裡 是 規定了一隻狼和一個好人說假話。

3.想到第二條之後 我開始考慮用什麼來儲存輸入資料呢?最終決定使用 map<int,int> ,可能當時有著遍歷方便的心理吧!

4.我動手寫出了主函式,雙重迴圈我考慮到 1 2 和 2 1 是一樣的情況 而且 不可能是 1 1 這型別(都是狼)的情況,所以第二層迴圈的起點是 第一層迴圈遍歷中的數值+1 。終點一致!

5.輸入決定單獨提出來寫在一個函式裡,map<>定義在全域性變數 ,N(總人數)也定義在全域性變數。這個方便呼叫,寫完輸入子函式以後。開始編寫 核心部分,判斷函式。

6.判斷函式 由於主函式規定了 遍歷是按照兩隻狼開始進行遍歷 所以顧名思義傳進來的必定是兩隻狼 所以判斷的子函式採用傳狼的機制 ,寫好引數表 我決定把錯誤的判斷放在中間進行判斷 最後 用true 來證明合法。就這樣 構建函式第一步完成。並規定好判斷錯誤要在 函式體return true 上完成。

7.轉回去看了一下題目,因為 題目中有一隻狼說謊 那麼潛臺詞就是告訴我們 另外一隻狼說的是真話。經過推導可以簡單得出幾個結論:

1》.那隻說謊的狼 取反 就變成 說真話的狼,假如改正後說真話的狼說 除了另外一隻狼外的人是狼 或說 另外一隻狼是好人 或說他自己是好人 則 這組則不成立。

2》.另外一隻沒說謊的狼 也按照上面邏輯。

8.推理完成以後,我打算用一個東西來存下所有推匯出的真話,思考了一會 決定使用 set<int> 來儲存真話!!

9.因為涉及到 ”取反”(其實就是把說謊話的狼取反),避免對原陣列的破壞 我打算使用 臨時map<int,int>來整體賦值原map<>

10.然後開始把我們知道的第兩個個真話(一個是說謊狼取反,另一個是說真話狼)放入 set<int,int>,(以下把放真話的set 稱為 事實箱子)。再把兩隻狼的事實也放入事實箱子,然後修改 臨時map<>的值。

11.事實箱子在正式使用之前要做一次碰撞,看看裡面的事實是否出現矛盾。矛盾就return false .再確認沒有問題之後 我 del 掉 放在臨時數組裡的狼。

12.這個時候 臨時map裡邊全是假定出來的 “好人” ,為了防止 沒有人說假話的情況 則 另外製定一個 變數=0;記錄好人說假話的人數,當且僅當遍歷完成之後的 好人說謊數量 等於 1 才被證明 是合法 否則是非法。

13.遍歷開始,遍歷的方法是 迭代器遍歷的方法。每次遍歷到一個map成員,使用事實箱子的一個 功能 count(); 我將一個map的成員的second 取反放入事實箱子的 count() 假如返回非零 則證明 有人說謊了。

14.那怎麼證明他沒有說謊呢?有的人說 除去上面的條件就是沒有說謊了唄!!其實不然,除去上面條件後 他還是可能說謊的。

為什麼呢?因為事實箱子是一邊 收集真話一邊判斷假話的,他不是已經有了全部的真話才判斷的,如果有人說 -3 而事實箱子裡面沒有 +3 難道就說後面 一個 說 +3 的人說了假話了嗎? 顯然不對。

15.所以 我還要判斷他說的是 負數(狼)情況,假如他說了 除兩隻狼外的 人是狼 那麼他在說謊。否則說的才是真話。

16.最後再判斷 好人幾個說了謊 。好了 判斷的函式就寫完了

// 思路全部結束   上程式碼!

#include <bits/stdc++.h>
#include <stdio.h>

using namespace std;
// 拆分的思想是錯誤的  聯合思想才是正確的

map<int, int>PX;
int N;

void input() {

	scanf("%d", &N);

	for (int i = 1, j = 0; i <= N; i++)
		PX[i] = (scanf("%d", &j), j);
}

bool PDSH(int LIEP1,int lastP2) { // P1 說謊
	map<int, int>TMP = PX;
	TMP[LIEP1] *= -1; // 取反
	// 雖然他說了謊 但他也是狼 對狼建立索引 set<> 
	set<int>LINK; LINK.insert(LIEP1); LINK.insert(lastP2);

	if (TMP[lastP2] < 0) return false; // 如果另一隻說真話的狼 說別人是狼 那麼就不成立
	if (TMP[LIEP1] < 0 && -TMP[LIEP1] != lastP2) return false; // 如果 改正說謊話的狼說除了另一隻狼以外是 狼 則也不成立。

	int HrLieCnt = 0;

	//建立說謊驗證索引  // 怎麼判斷 好人說了謊呢? 1.好人說已知狼以外的
	set<int> LP;
	// 將狼的事實存入 事實箱子
	LP.insert(TMP[LIEP1]);
	LP.insert(TMP[lastP2]);
	if (LP.count(LIEP1)) return false; // 檢查事實箱子 是否出現 相反事實
	if (LP.count(lastP2)) return false; // 檢查事實箱子 是否出現 相反事實
	LP.insert(-LIEP1);
	LP.insert(-lastP2);

	//DEL掉 臨時狼
	TMP.erase(LIEP1);
	TMP.erase(lastP2);

	for (map<int, int>::iterator it = TMP.begin(); it != TMP.end(); it++) {
		if (LP.count(-(it->second))) { // 已經存在一個相反事實 則證明他說假話  
			HrLieCnt++; //已證 好人說謊
		}
		else { // 他說的無從證實真假 
			// 開始判斷他說的話的真假
			if (it->second < 0) {
				if (LINK.count(abs(it->second))) { // 判斷是否指向傳入狼人
					// 指向說明講的是真話
					LP.insert(it->second);
				}
				else {
					HrLieCnt++; // 好人說謊
				}
			}
		}
	}
	if (HrLieCnt != 1) return false;
	return true;
}


int main() {

	input();
	bool flag = true;
	for (int i = 1; i <= N; i++)
		for (int j = i+1; j <= N; j++) {
			if (PDSH(i, j) || PDSH(j,i)) {
				printf("%d %d\n", i, j);
				flag = false;
				i = N + 10;
				break;
			}
		}
	if (flag)
		printf("No Solution\n");

	system("pause");

	return 0;
}