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;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值