来源:NOIP2003提高组 https://ac.nowcoder.com/acm/contest/251/B
明明同学最近迷上了侦探漫画《柯南》并沉醉于推理游戏之中,于是他召集了一群同学玩推理游戏。游戏的内容是这样的,明明的同学们先商量好由其中的一个人充当罪犯(在明明不知情的情况下),明明的任务就是找出这个罪犯。接着,明明逐个询问每一个同学,被询问者可能会说:
证词中出现的其他话,都不列入逻辑推理的内容。
明明所知道的是,他的同学中有N个人始终说假话,其余的人始终说真。
现在,明明需要你帮助他从他同学的话中推断出谁是真正的凶手,请记住,凶手只有一个!
输入描述:
输入由若干行组成,第一行有二个整数,M(1≤M≤20)、N(1≤N≤M)和P(1≤P≤100);M是参加游戏的明明的同学数,N是其中始终说谎的人数,P是证言的总数。接下来M行,每行是明明的一个同学的名字(英文字母组成,没有主格,全部大写)。
往后有P行,每行开始是某个同学的名宇,紧跟着一个冒号和一个空格,后面是一句证词,符合前表中所列格式。证词每行不会超过250个字符。
输入中不会出现连续的两个空格,而且每行开头和结尾也没有空格。
输出描述:
如果你的程序能确定谁是罪犯,则输出他的名字;如果程序判断出不止一个人可能是罪犯,则输出 Cannot Determine;如果程序判断出没有人可能成为罪犯,则输出 Impossible。
示例1
输入
3 1 5
MIKE
CHARLES
KATE
MIKE: I am guilty.
MIKE: Today is Sunday.
CHARLES: MIKE is guilty.
KATE: I am guilty.
KATE: How are you??
输出
MIKE
题解: 该题牛客直播讲解链接:https://www.nowcoder.com/study/live/248/1/5
接下来说一下我的想法:m n p,分别是人名个数,说假话的人数,以及说的话数量
注意:有些人可能没有说话,so有可能没有说话的都是假话,也有可能都是真话
所以:统计数据中得出的说假话的人数fake,以及没有说话人数other
若other都是真话,则说假话人数最小值应该为 fake ,
若other都是假话,则说假话人数最大值应该为 fake + other
so 假话人数应该介于最大最小值之间 即:fake <= n && n <= fake + other
这道题需要模拟假设某人是凶手,今天是星期几。在这两个限制条件下进行判断每句话的真假,进而得知说这句话的人是否诚实。
若判断完所有话,n的值并没有介于: fake <= n && n <= fake + other则说明该假设不成立。
n的值若符合条件,则返回main函数进行计数器++,记录当前的凶手
判断说的话:则需要判断前四种关于罪犯的话以及关于星期几的话:
类似于这种的:xxx is guilty.则需要将这句话的人物和话分开,调用函数get()函数进行分离,可以用str.find(" is guilty."); 用find函数找一下是不是这种话。
前四种关于罪犯的话不是,则可能是关于星期几的,for循环判断的到传入的星期几是否与开始假设的星期相同
这五种若都不是,则是废话直接返回。
知识点积累:关于字符串string的知识
1、str.find()函数
查找某个字符,或者查找某一串字符是否存在,存在则返回当前查找字符或字符串的起始位置下标;
eg: str.find(':'); str.find(" is not guilty.");
2、str.substr()函数,
截取某一段字符串并返回该字符串,参数为两个,第一个起始位置下标,第二个终止位置下标,若不写第二个参数则默认为末尾。
eg: string person = str.substr(0,t); string sent = str.substr(t+2);
3、memset()函数
头文件:cstring 参数:地址,初始化的值(只能初始化为-1和0),长度
int state[100];
memset(state,-1,sizeof(state));
具体题解:看代码中的注释。。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <stack>
#include <algorithm>
using namespace std;
typedef long long ll;
#define maxn 1000005
#define mod 7654321
#define NIL -1
const int N = 110;
int m,n,p;
//存储姓名和说的话
string name[N],words[N];
string weekday[7] =
{
"Today is Monday.",
"Today is Tuesday.",
"Today is Wednesday.",
"Today is Thursday.",
"Today is Friday.",
"Today is Saturday.",
"Today is Sunday."
};
int state[N];//每个人的状态 真:0 假:1 废话:-1
//返回当前名字的人物编号
int get_person(string str)
{
for(int i=0;i<m;i++)
{
if(str == name[i])
return i;
}
return -1;
}
//将话中的人物和话分离,匹配人物编号与话
pair<int,string> get(string str)
{
int t = str.find(':');
string person = str.substr(0,t);
string sent = str.substr(t+2);
int i = get_person(person);
return pair<int,string>(i,sent);
//或者:return {i,sent};
}
//返回若当前人物为凶手,当前day条件下每句话的状态 真:0 假:1 废话:-1
int get_state(int bad_man,int day,int person,string str)
{
if(str == "I am guilty.")
{
//若当前假设凶手 和当前人物陈述一样 则为真话
if(bad_man == person) return 0;
return 1;
}
if(str == "I am not guilty.")
{
//若当前假设凶手 和当前人物陈述一样 则为真话
if(bad_man != person) return 0;
return 1;
}
//不存在返回-1
int t = str.find(" is guilty.");
if(t != -1)
{
//获得当前话中的人物
string people = str.substr(0,t);
int p = get_person(people);
//若当前假设凶手 和当前人物陈述一样 则为真话
if(p == bad_man) return 0;
return 1;
}
t = str.find(" is not guilty.");
if(t != -1)
{
//获得当前话中的人物
string people = str.substr(0,t);
int p = get_person(people);
//若当前假设凶手 和当前人物陈述一样 则为真话
if(bad_man != p) return 0;
return 1;
}
//最后判断星期几
for(int i=0;i<7;i++)
{
//若当前话str是说的周几则找出当前周几的编号
if(weekday[i] == str)
{
if(i == day) return 0;
return 1;
}
}
//否则说的是废话,返回-1
return -1;
}
bool check(int bad_man,int day)
{
//头文件:cstring 参数:地址,初始化的值(只能初始化为-1和0),长度
//默认为废话-1,调用一次,初始化一次人物状态
memset(state,-1,sizeof(state));
for(int i = 0 ; i < p ; i++)
{
pair<int,string> t = get(words[i]);
int person = t.first;
//s 存储当前话的状态
int s = get_state(bad_man,day,person,t.second);
//判断是否始终说真话或始终说假话
if(s == 0)//若是真话
{
if(state[person] == 1)//若当前人物之前说过假话返回false
return false;
state[person] = s;//更新当前人物的状态
}
else if(s == 1)//若是假话
{
if(state[person] == 0)//若当前人物之前说过真话返回false
return false;
state[person] = s;//更新当前人物的状态
}
}
//统计m个人中说假话人数和没有说话的人数
int fake = 0,other = 0;
for(int i = 0 ; i < m ; i++)
{
if(state[i] == 1) fake++;
else if(state[i] == -1) other++;
}
//若other都是真话,则说假话人数最小值应该为 fake ,
//若other都是假话,则说假话人数最大值应该为 fake + other
//so 假话人数应该介于最大最小值之间
if(fake <= n && n <= fake + other)
return true;
else
return false;
}
int main()
{
cin>>m>>n>>p;
for(int i=0;i<m;i++)
cin>>name[i];
getchar();//接受回车
for(int i=0;i<p;i++)
getline(cin,words[i]);
//依次枚举凶手、星期几以及句子
int cnt = 0,p;
for(int i=0;i<m;i++)//凶手
{
for(int day=0;day<7;day++)//星期几
{
//枚举 若当前人物为凶手,说假话人数的范围(fake,fake+other)是否可能有 n
if(check(i,day))
{
p = i;//存储当前凶手编号
cnt++;//记录当前可能凶手个数
break;
}
}
}
if(cnt == 0) puts("Impossible");
else if(cnt == 1) cout<<name[p]<<endl;
else puts("Cannot Determine");
return 0;
}