回溯-DFS-PTA-回溯

Werewolf

Werewolf(狼人杀) is a game in which the players are partitioned into two parties: the werewolves and the human beings. Suppose that in a game,

player #1 said: “Player #2 is a werewolf.”;
player #2 said: “Player #3 is a human.”;
player #3 said: “Player #4 is a werewolf.”;
player #4 said: “Player #5 is a human.”; and
player #5 said: “Player #4 is a human.”.
Given that there were 2 werewolves among them, at least one but not all the werewolves were lying, and there were exactly 2 liers. Can you point out the werewolves?

Now you are asked to solve a harder vertion of this problem: given that there were N players, with M werewolves among them, at least one but not all the werewolves were lying, and there were exactly L liers. You are supposed to point out the werewolves.

Input Specification:
Each input file contains one test case. For each case, the first line gives three positive integer N (5 ≤ N ≤ 100), M and L (2 ≤ M,L < N). Then N lines follow and the i-th line gives the statement of the i-th player (1 ≤ i ≤ N), which is represented by the index of the player with a positive sign for a human and a negative sign for a werewolf.

Output Specification:
If a solution exists, print in a line in descending order the indices of the M werewolves. The numbers must be separated by exactly one space with no extra spaces at the beginning or the end of the line. If there are more than one solution, you must output the largest solution sequence – that is, for two sequences A = { a[1], …, a[M] } and B = { b[1], …, b[M] }, if there exists 0 ≤ k < M such that a[i] = b[i] (i ≤ k) and a[k+1]>b[k+1], then A is said to be larger than B. In case there is no solution, simply print No Solution.

Sample Input 1:

5 2 2
-2
+3
-4
+5
+4

Sample Output 1:

4 1

Sample Input 2:

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

Sample Output 2:

6 4

Sample Input 3:

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

Sample Output 3:

No Solution

这道题是基本的DFS/回溯思路。
设有total_n个人,从根节点开始,生长出一棵total_n叉树。每条从根到叶的路径都代表一种狼的存在情况(有些明显不符合要求的可以剪枝)。开始时设所有人均不是狼,iswolf[ ] 全设为0,注意在对该树进行DFS的过程中,回溯时要把 iswolf[i] 恢复为0. 否则在 i+1 子树的DFS过程中将被迫无法考虑 i

很多人习惯把如上的恢复原状称为“消除痕迹”或设visit来记录。我个人觉得这里容易产生歧义,因为这种恢复原状是为了避免子树之间的相互冲突。
如果一定要用visit来记录,也应该铭记该visit不是树上某一结点是否被访问的标志,它和树上某一结点不构成任何对应关系,而是“当前枚举情况下谁已经被枚举过了”的标志。与其称为visit,其实正是iswolf[ ]
应充分意识到,这种visit与对图进行深度优先搜索时避免重复节点的visit是完全不同的,后者这种visit才是与图上节点构成一一对应的。

My Answer:

#include <iostream>
#include <vector>
using namespace std;
typedef struct { char label; int obj; } Statement;

int total_n, wolf_n, lie_n;
vector<int> wolves;

bool DFS(vector<int> wolves, vector<Statement> word, vector<int>& iswolf);

int main(void)
{
	cin >> total_n >> wolf_n >> lie_n;

	vector<Statement> word(1);
	for (int i = 1; i <= total_n; i++) {
		Statement t;
		cin >> t.label >> t.obj;
		word.push_back(t);
	}

	vector<int> iswolf(total_n + 1, 0);
	vector<int> wolves;
	if (!DFS(wolves, word, iswolf)) {
		cout << "No Solution";
	}

	return 0;
}

bool DFS(vector<int> wolves, vector<Statement> word, vector<int>& iswolf)
{
	if (wolves.size() == wolf_n) {	// 1 若当前狼数符合条件
		int lier_cnt = 0;
		for (int i = 1; i <= total_n; i++) {
			if ((iswolf[word[i].obj] && word[i].label == '+') ||
				(!iswolf[word[i].obj] && word[i].label == '-')) lier_cnt++;
		}
		if (lier_cnt == lie_n) {	// 1.1 若此时说谎者数符合条件
			int lier_wolf_cnt = 0;
			for (int i = 1; i <= total_n; i++) {
				if (iswolf[i] &&
					((iswolf[word[i].obj] && word[i].label == '+') ||
					(!iswolf[word[i].obj] && word[i].label == '-'))) lier_wolf_cnt++;
			}
			if (lier_wolf_cnt >= 1 && lier_wolf_cnt <= wolf_n - 1) {	// 1.1.1 若此时是狼的说谎者的数量符合条件
				for (int i = 0; i < wolves.size(); i++) {				//输出 返回main()
					cout << wolves[i];
					if (i != wolves.size() - 1) cout << " ";
				}
				return true;	//一旦发现满足题意的情况 将一路不停地返回true
			}
		}
	}
	
	for (int i = total_n; i >= 1; i--) {	//从后往前找 找到的第一个答案一定就是字典序里最大的 其他答案不用管 
		if (!iswolf[i]) {
			iswolf[i] = 1;
			wolves.push_back(i);
			if (DFS(wolves, word, iswolf)) return true;	//一旦有true 就不断返回直达main()
			wolves.pop_back();
			iswolf[i] = 0;	//这一次结束后 循环的下一个周期会是另一棵子树 
							//这棵子树里枚举/标记的wolf不应该对下一棵子树产生影响
		}
	}

	return false;
}

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值