PAT 1148
C++
版
1.题意
简单版本的狼人杀游戏。所有的最终序列需满足如下关系:
- 有且仅有两个狼人,且其中的一个狼人在说谎
- 陈述内容不应该有冲突
2.分析
如何做这种题目?我分析了很久,想不出什么好的解法,于是采用暴力破解的方法。思路一如下:
- step 1:使用双层for循环,对陈述内容进行处理。假设i,j两人都是说谎的人。
- step 2:在此基础上分别假设i是狼人或者j是狼人(因为总有一个撒谎的人是狼人),这样得到了两个处理后的序列。
- step 3:对处理后的序列判断是否是正确的序列(所谓正确的序列指的是:是否刚好有两个狼人)就ok了。
举例如下:
输入数据
5
-2
+3
-4
+5
+4
双层for
循环的第一次处理是:假设1,2撒谎,当1是狼人时,得到的序列是:+2,-3,-4,+5,+4,-1
【因为1是狼人,所以需要追加一个-1】;而当2是狼人时,得到的序列是:+2,-3,-4,+5,+4,-2
【因为1是狼人,所以需要追加一个-2】。可以看到这两个解均不满足条件。第一个解中的狼人数超标,且存在矛盾选项;第二个解中的狼人数超标,且存在矛盾选项。
- step 2:按照step 1的思路,继续循环。找到满足情况的所有解。
- step 3:找到所有的解之后,按照下标进行排序,得到最终的正确解。如果没有解的话,则输出
No Solution
。
3.代码
include<cstdio>
#include<set>
#include<algorithm>
#include<iostream>
#define maxn 105
using namespace std;
int N;
set<int> a;
struct result{
int a;
int b;
};
result res[maxn];
//判断数组array是否是一个标准的结果
bool judge(){
//判断是否有前后矛盾的答案
for(set<int>::iterator it1=a.begin();it1!=a.end();it1++){
for(set<int>::iterator it2=a.begin();it2!=a.end();it2++){
if((*it1) == (-1) * (*it2)) return false;
}
}
int count = 0;
for(set<int>::iterator it = a.begin();it!=a.end();it++){
if(*it < 0) count++;
}
if(count != 2) return false;//说明狼人数错误
return true;
}
int index = 0;
void printRes(){
int count = 2;
int res1 , res2;//保存狼人的结果
for(set<int>::iterator it2 = a.begin();it2!=a.end();it2++){
if( (*it2) < 0 && count > 1 ) {
res[index].b = *it2 * (-1);
res1 = *it2 *(-1);
count --;
}
else if((*it2) < 0 ){
res[index].a = *it2 * (-1) ;
res2 = *it2 * (-1);
count --;
break;//处理完全了
}
}
index++;
//cout<<res2 << " "<< res1 << endl;
}
bool cmp(result r1,result r2){
if(r1.a == r2.a) return r1.b < r2.b;
return r1.a < r2.a;
}
int main(){
int i,j,k;
int info[maxn];
cin >> N;
for(i = 1;i<= N;i++){
cin >> info[i];
}
//假设撒谎的是i和 j 两个人
for(i = 1;i <= N ;i++){
for(j = i+1;j <= N;j++){
cout <<"i = "<<i<<",j = "<< j<<"\n";
a.clear();//先把所有的元素删除
//将当前的元素添加到set 中
for(k =1 ;k<=N;k++){
if(k == i || k==j) {//插入相反的元素
a.insert( ((-1)*info[k]) );
}
else {//否则 插入 info[k]
a.insert(info[k]);
}
}
//如果i是狼人
a.insert(-i);
if(judge()) printRes();
//如果j是狼人
a.erase(-i);//先将上面的 -i 删除,然后添加 -j
a.insert(-j);
if(judge()) printRes();
}
}
if(index == 0){
cout << "No Solution"<<"\n";
}else{
sort(res,res+index,cmp);
cout << res[0].a<<" "<<res[0].b;
}
}
4.测试用例
6
+6
+3
+1
-5
-2
+4
1 5
5
-2
+3
-4
+5
+4
1 4
5
-2
-3
-4
-5
-1
No Solution
5
+2
+3
+4
+5
+1
1 2
//测只有一个狼人在lie
5
-2
+3
+4
-5
-4
2 4
5
-2
+3
+4
-1
+4
1 3
5
-2
+3
+4
-1
-4
1 4
5
-2
+5
+4
-1
+4
1 5
5.执行结果
6.总结
- 使用set保存最终的判断序列,可以去除相同的数据
7.其它
7.1主要思路
在网上看了其它的解法。其主要思想如下:
每个人说的数字保存在vector数组中;
i从1~n、j从i+1~n遍历,分别假设i和j是狼人,a数组表示该人是狼人还是好人,等于1表示是好人,等于-1表示是狼人。【这里的数组a表示真正的情况】
k从1~n分别判断k所说的话是真是假,k说的话和真实情况不同(即v[k] * a[abs(v[k])] < 0
)则表示k在说谎,则将k放在lie数组中;
遍历完成后判断lie数组,如果说谎人数等于2并且这两个说谎的人一个是好人一个是狼人(即a[lie[0]] + a[lie[1]] == 0)表示满足题意,此时输出i和j并return,否则最后的时候输出No Solution~
其中较难理解的地方是:k从1~n分别判断k所说的话是真是假,k说的话和真实情况不同(即v[k] * a[abs(v[k])] < 0)则表示k在说谎,则将k放在lie数组中。这里我给出一下解释。
例如,对于如下输入:
5
-2
+3
-4
+5
+4
- 当i=1,j=2,k =1时,a[1]=a[2]=-1。v[1] = -2,说明2是狼人;
-
a[abs(v[k])] = a[2]
,又因为a[2]
表示的是:2是好人还是狼人【1是好人,-1为狼人】; - 所以当a[2]=1时,则
v[k] * a[abs(v[k])] < 0
则表示k=1这个人在说谎,如果当a[2]=-1时,则v[k] * a[abs(v[k])] > 0
则表示k=1这个人未说谎。
7.2 代码
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> v(n+1);
for (int i = 1; i <= n; i++) cin >> v[i];
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
vector<int> lie, a(n + 1, 1);
a[i] = a[j] = -1;
for (int k = 1; k <= n; k++)
if (v[k] * a[abs(v[k])] < 0) lie.push_back(k);
if (lie.size() == 2 && a[lie[0]] + a[lie[1]] == 0) {
cout << i << " " << j;
return 0;
}
}
}
cout << "No Solution";
return 0;
}
7.3 参考文章
=== update on 2019 05 07 ==
1.重要更新!!!
针对我自己的思路,即第2点中的“分析”,再次仔细分析这道题目,结合网友的博文,终于知道其坑在哪里了。
之前写的代码一直有两个测试用例过不了:
心里很纳闷儿,觉得没理由的啊,思路分析都是很清楚的。为什么就不能AC 呢?
在博客一文中,我找到了线索:
我同这个博主的想法是一样的,都是想先假设有两个人说谎了,然后再假设其中一人是狼人,这样就能找出所有的狼人了,但是事实并非如此!!因为可能存在一种情况:a是狼人,但是a却从没有在陈述中出现过,这样在上述的代码里就会漏掉这种情况,比如对于测试用例:
5
-2
-5
+4
-1
+4
1 3
其执行结果实际上应该是1 3
,但是通过我的代码执行得到的就是No Solution
。
知道问题在哪里就好办了,就是换思路,改代码呗!