【算法学习笔记】28.枚举法 解题报告 SJTU OJ 1255 1256 魔戒

Description

在前往末日火山的途中,佛罗多与他的霍比特人同胞不幸被半兽人抓住了。半兽人要对每个霍比特人进行询问,以找出哪个霍比特人携带了至尊魔戒。每个霍比特人可能会说以下几种话:

I have the ring. 我有魔戒。

I have not the ring. 我没有魔戒。

XXX has the ring. XXX有魔戒。(XXX表示某个霍比特人的名字)

XXX has not the ring. XXX没有魔戒。

Today is XXX. 今天天气真好,是XXX吧!(XXX表示Monday/Tuesday/Wednesday/Thursday/Friday/Saturday/Sunday其中之一,只有首字母大写)

询问中所回答的其他话,都不列入考虑的范围之内。

半兽人所知道的是,霍比特人中有N个人始终说假话,而其他人始终说真话。

每个霍比特人可能会回答多句话也可能不会回答。

必有且仅有一个霍比特人携带了魔戒。(霍比特人中不一定有人叫佛罗多,且携带者不一定是佛罗多哦~)

现在,半兽人希望从中推断出谁是真正的魔戒持有者!

Input Format

输入由若干行组成,第一行有二个整数,M(1<=M<=10)、N(1<=N<=M)和P(1<=P<=20)。

M是半兽人抓到的霍比特人数,N是其中始终说谎的人数,P是得到的回答的总数。接下来M行,

每行是一个霍比特人的名字(英文字母组成,全部大写)。

往后有P行,每行开始是某个霍比特人的名宇,紧跟着一个冒号和一个空格,后面是一句回答,符合前表中所列格式。回答每行不会超过250个字符。

输入中不会出现连续的两个空格,而且每行开头和结尾也没有空格。

Output Format

如果你的程序能确定谁持有了魔戒,则输出他的名字。如果程序判断出不止一个人可能持有了魔戒,则输出 I am confused. 。如果程序判断出没有人可能持有魔戒,则输出 It is impossible! 。

Sample Input

3 1 5
FRODO
SAM
GOLLUM
FRODO: I have the ring.
FRODO: Today is Sunday.
GOLLUM: FRODO has the ring.
SAM: I have the ring.
SAM: How are you??

Sample Output

FRODO

Hint

对于60%的数据,将不涉及到日期。

使用getline(cin,变量名)来读入整行的字符串。

使用getchar()来读入可能有的多余的字符(如回车等等)。

使用字符串.substr(起始位置,长度)来截取字符串。

1255是输出每句非废话的数字代码 其实就是这道题的init部分 各种字符串处理..比较容易错的是由于没有对后半部分完全匹配,而是单纯地提取特征子串来判断,会导致出错。(比如 I has....等废话)

string weekdays[] = {"Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"};
int M,N,P;
string names[10];
//是废话反悔人数+1 否则返回被指控的人的id
int isRubbish(string str,int speakerID){ 
    if(str==" I have the ring." || str==" I have not the ring.")
        return speakerID;
    for (int i=0; i<M; ++i) {
        string toCheck_1 = " "+names[i]+" has the ring.";//完全匹配
        string toCheck_2 = " "+names[i]+" has not the ring.";
        if(str==toCheck_1||str==toCheck_2)
            return i;
    }
    return M+1;
}

int main(int argc, char const *argv[])
{
    
    cin>>M>>N>>P;
    //各种字符串处理
    string statements[20];
    for (int i = 0; i < M;)
    {
        string temp ;
        getline(cin,temp);
        if(temp!="")
        {
            names[i++] = temp;
        }
    }
    int flag1[20]={0},flag2[20]={0},flag3[20]={0};
    for (int i = 0; i < P; ++i)
    {
        getline(cin, statements[i]);
        //flag1
        unsigned long loc = statements[i].find(":");
        int speakerid=0;
        if(loc!=-1){
            string nameCheck = statements[i].substr(0,loc);
            
            for (; speakerid<M; ++speakerid) if(names[speakerid]==nameCheck){
                flag1[i]=speakerid;
                break;
            }
            //flag2
            unsigned long frstB=0,scndB=0;
            frstB = statements[i].find(" ");//第一个空格位置
            scndB = statements[i].substr(frstB+1,statements[i].length()).find(" ");//第二个空格位置
            
            string f2 = statements[i].substr(frstB+1,scndB);//第一个单词 f2
            if(f2=="Today"){//说明是日期
                int weekdayId = 1;
                for (; weekdayId<=7; ++weekdayId) if(statements[i].find(weekdays[weekdayId-1])!=-1){
                    flag2[i]=weekdayId;
                    flag3[i]=3;
                    break;
                }
            }else{//描述戒指 但是要注意 这个也有可能是含有名字和I的废话
                int receivedId = isRubbish(statements[i].substr(loc+1),speakerid);
                if(receivedId==M+1)//判断是不是废话
                    continue;//是废话 终止此次循环 flag[3]保持0
                flag2[i]=receivedId;
                flag3[i]=(statements[i].find("not")==-1) ? 1 : 2;
            }
        }
        
    }
    //1255的输出部分
    // int rubbish=0;
    // for (int i = 0; i < P ; ++i) {
    //     if(flag3[i]!=0){
    //         cout<<flag1[i]<<" "<<flag2[i]<<" "<<flag3[i]<<endl;
    //     }else
    //         rubbish++;
    // }
    // if(rubbish==P)//全是废话
    //     cout<<"Orz"<<endl;
    

    /**********1256的部分开始了*******/

    //用枚举法(代入法)试试 ....= = 注意 星期几的正确性 也是在判断范围之内的
    int specious = 0,sid=0;
    //先计算一下只说废话和根本没说话的人..
    int rubbishPeople = 0;
    for (int i =0; i < M; ++i) {
        int yes = 1; int no_talk = 1;
        for (int j=0; j<P; ++j) if(flag1[j]==i) {//这个人说话了
            no_talk = 0;
            if(flag3[j]!=0)//这个人说的不是废话
            {   yes = 0;break;  }
        }
        if(yes or no_talk) //只说废话 或者 根本没说话
            rubbishPeople++;
    }
    //枚举罪犯和日期 
    int checked[15] = {0};//0表示初始 1表示说了谎 2表示真话
    for (int i = 0; i < M; ++i){
        for (int j=1; j < 8; ++j){//假设i是带戒指的人 且今天是周j
            int people = 0;//people是说谎的人数
            int ok = 1;
            memset(checked, 0, sizeof(checked));
            for(int k=0 ;k<P; ++k ){
                if((flag3[k]==3 and flag2[k]!=j) or ((flag2[k]==i and flag3[k]==2) or (flag2[k]!=i and flag3[k]==1)))//说谎
                {
                    if(checked[flag1[k]]==2)//之前说过真话
                        {ok=0;break;}
                    checked[flag1[k]]=1;//之前没说过真话
                }
                if((flag3[k]==3 and flag2[k]==j) or ((flag2[k]==i and flag3[k]==1) or (flag2[k]!=i and flag3[k]==2)))//说真话
                {
                    if(checked[flag1[k]]==1)//之前说了谎话
                        {ok=0;break;}
                    checked[flag1[k]]=2;
                }
            }
            if(!ok)//!ok表示有人在这种假设下 又说了真话又说了假话
                continue;//换一个日期
            for(int t = 0; t<M; t++)
            {
                if(checked[t]==1)
                    people++;//统计说谎的人数
            } 
            if(people<=N and people>=N-rubbishPeople)//此处认为rubbishPeople两种都有可能
            {
                specious++;//嫌疑人++
                sid = i;
                if(specious>1)
                {
                    cout<<"I am confused."<<endl;
                    return 0;
                }
                break;//换下一个人调查 此种环境下找到了一个嫌疑人就可以换嫌疑人了
            }
        }
    }
    //1256的输出
    if(specious==1)
        cout<<names[sid]<<endl;
    else if(specious==0){
        cout<<"It is impossible!"<<endl;
    }
    return 0;
}
1255+1256的双枚举

解决1256的第一种方法就是 枚举带戒指的人 和 日期 ,然后在当前的假设下,判断说谎人数是否可行。

第二种思路是这样的 就是先枚举带戒指的人,找到这种情况下,说真话的人的序列,认为这个序列的第一个说过和日期相关话的人说的话是真的,那么我们就得到了当前的日期,如果其他说真话的人说了其他的日期或者说谎话的人对日期说了真话 那么都认为这种假设(对于带戒指的人的假设)是错误的。

代码就不写了。。太累。。貌似可以优化一下。但是也优化不到那里去。因为在内层也要对所有的有关日期的话进行循环遍历。

原文地址:https://www.cnblogs.com/yuchenlin/p/sjtu_oj_1256.html