ZJU-ICPC Summer 2020 Contest 8 B-Picnic
剧毒的逻辑推断题,调了我整整7h!!
题目大意
你需要猜一个四位密码\(abcd\),\(1 \leq a,b,c,d \leq 9\)
现在有15个可能的密码,答案在15个密码中,有且仅有1个可行解。
现有四个人,他们非常聪明且诚实,他们对密码的讨论如下:
密码是Marisa设的。
Cirno: Marisa 告诉了我\(a=1\)。
Sunny: Marisa 告诉了我\(b\)是多少。
Luna: Marisa 告诉了我\(c\)是多少。
Star: Marisa 告诉了我\(d\)是多少。
Sunny: 我不知道密码\(abcd\)是多少。
Luna: 我不知道密码\(abcd\)是多少。
Star: 我不知道密码\(abcd\)是多少。但是我保证 Sunny 不知道\(d\)是多少。
Luna: 我知道\(d\)是多少了。
Sunny: 我知道\(abcd\)是多少了。
Luna: 我说谎了。实际上在你说“我知道\(abcd\)是多少了”之前我不知道\(d\)是多少。但是我现在知道\(abcd\)是多少了。
Sunny: 噢,我前一个的判断是错的,因为你说谎了。现在我也知道答案\(abcd\)是多少了。
Star: 我也知道\(abcd\)是多少了
以上为所有对话,是直接从英文翻译过来的。
附上英文题面:
One day Cirno, Sunny Milk, Luna Child, Star Sapphire, Marisa Kirisame went picnicking. While Marisa was at WC, the other four were chatting about Marisa's new password, a 4-digit integer, abcd,1≤a,b,c,d≤9. They found a note from Marisa's bag with 15 integers among which the password is one of them.
Since they are with Cirno, Sunny, Luna and Star are incredibly smart. Though fairies like playing tricks, they are honest. So each statement is true unless someone says it's a lie.
Following is their chatting.
Cirno: Marisa told me a=1.
Sunny: Marisa told me b.
Luna: Marisa told me c.
Star: Marisa told me d.
Sunny: I don't know abcd.
Luna: I don't know abcd.
Star: I don't know abcd, butI'm sure Sunny doesn't know d.
Luna: I know d.
Sunny: I know abcd now.
Luna: I told a lie. In fact I didn't know d before you said "I know abcd now.". But now I know abcd.
Sunny: Oh my previous induction was false since you told a lie. Now I know the true abcd.
Star: I know abcd, too.
算法分析
解释一下每句话的意思
密码的第一位是\(1\)
废话
废话
废话
仅仅知道\(b\),不能知道\(abcd\),意思是所有密码里的\(b\)没有独一无二的
仅仅知道\(c\),不能知道\(abcd\),意思是所有密码里的\(c\)没有独一无二的
仅仅知道\(d\),不能知道\(abcd\),意思是所有密码里的\(d\)没有独一无二的;知道\(b\)一定不能知道\(d\),说明\(b\)不能只对应一种\(d\)
假话,但是字面意思说明\(c\)只对应一种\(d\),获得\(c\)的范围。
在上一句话限定的\(c,d\)范围内,\(b\)能唯一对应一对\(c,d\),即\(b\)只对应一个\(c\)或一个\(d\) 获得\(b\)的真实范围。
说谎,说明知道\(c\)并不能知道\(d\),之前求出\(c\)的范围全是错的。此时知道了通过\(c\)和\(b\)的范围求出了\(abcd\),获得\(a\)的真实范围。
此时利用\(b,c\)的范围准确知道\(abcd\),进一步缩小\(b\)的范围。
此时利用\(d\)和\(b,c\)的范围知道\(abcd\),求出答案。
注意
-
"仅仅知道d,不能知道abcd,意思是所有密码里的d没有独一无二的;知道b一定不能知道d,说明b不能只对应一种d"里,先处理后面的条件,再处理前面的条件。(分号隔开)
-
注意一个和一种的区别,能唯一确定\(abcd\)的是一个。
-
假话可以反向理解,假话求出的范围可以用来排除。
其余就是代码实现的问题了,每个人都不一样。
#include<bits/stdc++.h>
using namespace std;
char s[20][5];
int fake[20];//密码是否已经判断出真假
int Binb[12],Binc[12],Bind[12];//Binb[i] 表示b == i的密码个数,其余同理。
int Binbc[12][12],Binbd[12][12],Bincd[12][12];//Binbc[i][j]表示满足b==i&&c==j的密码个数,其余同理。
int crb[12],crc[12],crd[12];//用于统计b,c,d的范围,具体意义不定。
void init(){//初始化
memset(s,0,sizeof(s));
memset(fake,0,sizeof(fake));
memset(Binb,0,sizeof(Binb));
memset(Binc,0,sizeof(Binc));
memset(Bind,0,sizeof(Bind));
memset(Binbc,0,sizeof(Binbc));
memset(Binbd,0,sizeof(Binbd));
memset(Bincd,0,sizeof(Bincd));
memset(crb,0,sizeof(crb));
memset(crc,0,sizeof(crc));
memset(crd,0,sizeof(crd));
}
void del(int id){//确定了一条密码为假,删去
fake[id] = 1;
Binb[s[id][1] - '0'] --;
Binc[s[id][2] - '0'] --;
Bind[s[id][3] - '0'] --;
Binbc[s[id][1] - '0'][s[id][2] - '0'] --;
Binbd[s[id][1] - '0'][s[id][3] - '0'] --;
Bincd[s[id][2] - '0'][s[id][3] - '0'] --;
if(!Binb[s[id][1] - '0']) crb[s[id][1] - '0'] = 0; //若剩下的密码b位上某一种数字没有了,则b范围修改。
if(!Binc[s[id][2] - '0']) crc[s[id][2] - '0'] = 0;
if(!Bind[s[id][3] - '0']) crd[s[id][3] - '0'] = 0;
}
int main(){
int T; scanf("%d",&T);
while(T --){
init(); // init
for(int i = 1; i <= 15; ++ i) scanf("%s",s[i]); //readin
for(int i = 1; i <= 15; ++ i){
fake[i] = 0;
Binb[s[i][1] - '0'] ++;
Binc[s[i][2] - '0'] ++;
Bind[s[i][3] - '0'] ++;
Binbc[s[i][1] - '0'][s[i][2] - '0'] ++;
Binbd[s[i][1] - '0'][s[i][3] - '0'] ++;
Bincd[s[i][2] - '0'][s[i][3] - '0'] ++;//添加
} // update for first
for(int i = 15; i >= 1; -- i){
bool flag = 0;
for(int j = i - 1; j >= 1; -- j){
if(s[j][1] == s[i][1] && s[j][2] == s[i][2] && s[j][3] == s[i][3] && s[j][0] == s[i][0]) { flag = 1; break; }//去重,相同的密码只保留一个。
}
if(flag) del(i);
}
//首位为1
for(int i = 1; i <= 15; ++ i){
if(fake[i]) continue;
if(s[i][0] - '0' != 1) del(i);
}//first 1
//独一无二的b
for(int i = 1; i <= 15; ++ i){
if(fake[i]) continue;
if(Binb[s[i][1] - '0'] == 1) del(i);
}//unique b
//独一无二的c
for(int i = 1; i <= 15; ++ i){
if(fake[i]) continue;
if(Binc[s[i][2] - '0'] == 1) del(i);
}//unique c
//知道b不能知道d,所有 ( 对应多个d的b ) 对应的d都排除
memset(crd,0,sizeof(crd));
for(int i = 1; i <= 9; ++ i){//b
int kind = 0;
for(int j = 1; j <= 9; ++ j){//d
if(Binbd[i][j]) ++ kind;
}
if(kind == 1){//this d can't be true
for(int j = 1; j <= 9; ++ j){
if(Binbd[i][j]) crd[j] = 1;
}
}
}
for(int i = 1; i <= 15; ++ i){//根据范围删去
if(fake[i]) continue;
if(crd[s[i][3] - '0']) del(i);
}
//unique b -> d
//独一无二的d
for(int i = 1; i <= 15; ++ i){
if(fake[i]) continue;
if(Bind[s[i][3] - '0'] == 1) del(i);
}//unique d
//对应唯一一种d 的c
memset(crc,0,sizeof(crc));
for(int i = 1; i <= 9; ++ i){//c
int kind = 0;
for(int j = 1; j <= 9; ++ j){//d
if(Bincd[i][j]) ++ kind;
}
if(kind == 1){
crc[i] = 1;
}
}
//what c can know d
//but c maybe not true
//对应上面c中唯一一个 的b
memset(crb,0,sizeof(crb));
for(int i = 1; i <= 9; ++ i){//b
int sum = 0;
for(int j = 1; j <= 9; ++ j){//c
if(!crc[j]) continue;
sum += Binbc[i][j];
}
if(sum == 1){
crb[i] = 1;
}
}
for(int i = 1; i <= 15; ++ i){//根据范围删去
if(fake[i]) continue;
if(!crb[s[i][1] - '0']) del(i);
}
//through these c, what b can know b,c,d
// these b is true
//上面求出来的c都是错的
for(int i = 1; i <= 15; ++ i){//根据范围删去
if(fake[i]) continue;
if(crc[s[i][2] - '0']) del(i);
}//对应 唯一一个上面求出的b 的c
memset(crc,0,sizeof(crc));
for(int i = 1; i <= 9; ++ i){// c
int sum = 0;
for(int j = 1; j <= 9; ++ j){// b
if(!crb[j]) continue;
sum += Binbc[j][i];
}
if(sum == 1){
crc[i] = 1;
}
}
for(int i = 1; i <= 15; ++ i){//根据范围删去
if(fake[i]) continue;
if(!crc[s[i][2] - '0']) del(i);
}
//through these b, some c can know abcd
//对应 唯一一个上面求出的c 的b
memset(crb,0,sizeof(crb));
for(int i = 1; i <= 9; ++ i){//b
int sum = 0;
for(int j = 1; j <= 9; ++ j){//c
if(!crc[j]) continue;
sum += Binbc[i][j];
}
if(sum == 1){
crb[i] = 1;
}
}
for(int i = 1; i <= 15; ++ i){//根据范围删去
if(fake[i]) continue;
if(!crb[s[i][1] - '0']) del(i);
}
//through these c, some b can know abcd
//求d
for(int i = 1; i <= 15; ++ i){
if(fake[i]) continue;
if(Bind[s[i][3] - '0'] == 1) { printf("%s\n",s[i]); break; }
}
//know d, know abcd
}
return 0;
}