题解 P2482 【[SDOI2010]猪国杀】

· · 题解

猪年总得写一道和猪有关的大模拟吧

第一次给黑题写题解,可能写得不怎么好qwq

这道题就是大模拟,没有任何方法可言,大约300行左右的代码。一定要耐心,不断找出程序中的小BUG,然后才能解决。数据可以从loj.ac下载。

前面的题解已经把很多东西说得很明白了,我就来说一说我掉过的一些坑吧。

1.决斗可以对任何猪释放,不需要相邻,反猪必定对主猪释放,其他猪对后面一只与它敌对的猪释放。
2.无懈可击可以循环释放,最好用递归实现。
3.要实时更新每只猪的显现身份和存活情况,遇到游戏结束及时返回。
4.除了桃以外,其他所有的牌释放后都必须从第一张开始重新模拟,因为前面的牌可能又能够使用了。
5.牌堆没有牌时,要不断最后一张牌。

下面是程序分段讲解:

Part0:定义和小函数

关于每只猪的struct定义

struct player{
    int id;//真实身份
    int hp;//生命
    int cnt;//牌数
    char card[M];//牌
    int pre;//前面的那只猪
    int nxt;//后面的那只猪
    int pigid;//暴露身份
    bool special;//是否为类反猪
    bool weapon;//是否装备武器
}pig[N];

多用用小函数,可以使程序变得更加简洁,从而不至于达到600-1000行的恐怖水平。

a.打印第i只猪的手牌:

大家可能会把它写进主程序,因为它只会用到一次,但是在调试中这段语句可以帮你大忙。

void printcard(int i){
    bool first=true;
    for (int j=1;j<=pig[i].cnt;++j){
        if (pig[i].card[j]=='X') continue;
            if (!first){
                printf(" ");
            }else first=false;
        printf("%c",pig[i].card[j]);
    }
    printf("\n");
}

b.寻找并扣除卡牌:

找到卡牌就返回true并扣除,没找到就返回false

int findcard(int x,char card){//x代表是哪头猪,card表示卡牌符号
    for (int i=1;i<=pig[x].cnt;++i){
        if (pig[x].card[i]==card){
            pig[x].card[i]='X';
            return true;
        }
    }
    return false;
}

c.摸牌:

这个如果不写到过程里去的话,那我很佩服你的意志。

void getcard(int i){
    if (top>m) top=m;//牌堆没牌了,就不断摸最上面一张
    pig[i].cnt++;
    pig[i].card[pig[i].cnt]=cards[top];
    top++;//top表示牌堆的顶部
}

Part1:扣血的实现

这一次,我们把扣血和死亡结算放在一块运行。

我们考虑一头猪死了,如何更新整个环(链表?)继续运行下去,这就是我为什么要开设nxtpre了,一个前导和后继可以解决所有问题。

$pig[pig[i].nxt].pre=pig[i].pre;$它后面的猪指向它前面的猪 事实上,如果用暴力$O(n)$求解的话,也不会超时。 ``` void dechp(int x,int i){//x是伤害来源者,i是伤害承受者 pig[i].hp--;//扣血 while (pig[i].hp<=0 && findcard(i,'P')){ pig[i].hp++;//如果有桃赶快用 } if (pig[i].hp<=0){//如果血还是没到0就回天乏术了 pig[i].cnt=0; pig[i].weapon=false;//清空这个人的资料 if (pig[i].id==1){ gameover=true;//主猪死了游戏立刻结束 return; }else if (pig[i].id==2){ if (pig[x].id==1){//主猪杀了忠猪,按题目要求清空主猪的手牌 pig[x].cnt=0; pig[x].weapon=false;//别忘了清空武器 } }else if (pig[i].id==3){ FPcnt--;//反猪的数量减1 if (FPcnt==0){//如果已经没有反猪的话 gameover=true;//游戏结束 return; } getcard(x);getcard(x);getcard(x);//摸牌,记得千万不要放在上面的if语句前面 } pig[pig[i].pre].nxt=pig[i].nxt;//更新前驱后继 pig[pig[i].nxt].pre=pig[i].pre; } } ``` ### $Part2:$关系的实现 下面函数中,第一个表示询问$x$和$y$是不是敌人,第二个则询问两人是不是同伙,第三个表示$x$向$y$献殷勤,第四个表示$x$向$y$表敌意。 询问或变更的时候要以$x$出发,应当判断$x$的真实身份和$y$的显露身份。$y$没有显露身份则不能进行下去。 ``` bool enemy(int x,int y){ if (pig[x].id==1 && pig[y].special==true) return true;//对主猪而言,类反猪是敌人 if (pig[y].pigid==0) return false;//y没有显露身份,返回false if (pig[x].id!=3 && pig[y].pigid==3) return true;//对其他猪而言,身份不符的是敌人 if (pig[x].id==3 && pig[y].pigid!=3) return true; return false; } bool friendly(int x,int y){//同理 if (pig[y].pigid==0) return false; if (pig[x].id!=3 && pig[y].pigid!=3) return true; if (pig[x].id==3 && pig[y].pigid==3) return true; return false; } void getfriend(int x,int y){ if (pig[y].pigid==0) return;//如果y没有显露身份,交朋友没有意义 if (pig[y].pigid!=3) pig[x].special=false,pig[x].pigid=1;//否则就显露和y相同的身份,记得处理类反猪的头衔。 if (pig[y].pigid==3) pig[x].special=true,pig[x].pigid=3; } void getenemy(int x,int y){//同理 if (pig[y].pigid==0) return; if (pig[y].pigid!=3) pig[x].pigid=3,pig[x].special=true; if (pig[y].pigid==3) pig[x].pigid=1,pig[x].special=false; } ``` ### $Part3:$部分卡牌的使用 这里的部分卡牌,包括: 万箭齐发,决斗,南猪入侵,杀(闪),无懈可击 #### $a.$杀(闪) 相对于其他几部分,这一部分相对容易些。 ``` void usecard_k(int from,int to){ getenemy(from,to);//这是一种表敌意的行为 bool p=findcard(to,'D');//尝试去目标的牌库寻找闪 if (p==false){ dechp(from,to);//找不到,就扣血 } } ``` #### $b.$决斗 决斗被无懈可击挡住的情况我们在这里暂不讨论,这里讨论的是如果$from$对$to$的决斗生效的情况。 ``` void usecard_f(int from,int to){ if (pig[from].id==1 && pig[to].id==2){//如果是主猪对忠猪下的手,忠猪不会还手 dechp(from,to); return; } while (true){ bool flag1=findcard(to,'K');//轮流在两人的牌库里寻找杀,找不到就扣血,退出。 if (!flag1){ dechp(from,to); return; } bool flag2=findcard(from,'K'); if (!flag2){ dechp(to,from);//如果自己出的牌害了自己,则需要把两人的攻击关系反一下 return; } } } ``` #### $c.$南猪入侵,万箭齐发 这两个可以放在一起讨论。 ``` void usecard_n(int from,int to){//南猪入侵 while (true){ if (gameover) return;//永远要注意实时更新游戏是否结束 if (!usecard_j(from,to,0)){//如果没有被无懈可击挡住的话 bool flag=findcard(to,'K');//在牌库中寻找杀 if (!flag){ if (pig[to].id==1 && pig[from].pigid==0){ pig[from].special=true;//如果是忠猪误伤了主猪,且忠猪还未表露身份,则视为类反猪 } dechp(from,to);//扣血 } } to=pig[to].nxt; if (to==from) return;//一圈轮完,就退出 } } void usecard_w(int from,int to){//万箭齐发 while (true){ if (gameover) return; if (!usecard_j(from,to,0)){ bool flag=findcard(to,'D');//把这里改成D就可以了 if (!flag){ if (pig[to].id==1 && pig[from].pigid==0){ pig[from].special=true; } dechp(from,to); } } to=pig[to].nxt; if (to==from) return; } } ``` #### $d.$无懈可击 这里就显得比较复杂了。我们用$from$表示锦囊牌的发出者,$to$表示锦囊牌的承受者,$last$表示上一个无懈可击的发出者。$last$初始为$0$。 ``` bool usecard_j(int from,int to,int last){ int now=from;//轮流开始出无懈可击 while (true){ if ((last==0 && friendly(now,to)) || (last!=0 && enemy(now,last))){//如果你和锦囊牌的承受者是朋友或者和上一张无懈可击的发出者是敌人,那么就是用无懈可击。last的值确定了是哪种情况 if (findcard(now,'J')){//如果有无懈可击可以出 if (last==0 && friendly(now,to)) getfriend(now,to); if (last!=0 && enemy(now,last)) getenemy(now,last);//朋友和敌人关系更新,因为这是献殷勤和表敌意行为 if (!usecard_j(pig[now].nxt,to,now)){ return true;//如果没有被其他无懈可击抵消,那么这张无懈可击就是有效的 } } } now=pig[now].nxt; if (now==from) return false;//绕完一圈就退出 } } ``` ### $Part4:$读入/初始化/结束部分 嗯,没错,我们先讲这个,最后再来讲如何运行整个游戏。 ``` int main(){ scanf("%d%d",&n,&m); for (int i=1;i<N;++i){ for (int j=1;j<M;++j){ pig[i].card[j]='X';//X表示这个位置是空位 } pig[i].hp=4;//初始生命为4 } for (int i=1;i<=n;++i){ char names[5],s[5]; scanf("%s",names); for (int j=1;j<=4;++j){ scanf("%s",s); pig[i].card[j]=s[0];//依次读入4张牌 } pig[i].cnt=4; if (names[0]=='M') pig[i].id=1,pig[i].pigid=1;//主猪的身份一开始就暴露的 else if (names[0]=='Z') pig[i].id=2; else pig[i].id=3,FPcnt++;//反猪,计数器加一 pig[i].nxt=i+1;//前驱后继的初始化 pig[i].pre=i-1; } pig[n].nxt=1;//特殊的前驱后继,构成整个环 pig[1].pre=n; for (int i=1;i<=m;++i){ char s[5]; scanf("%s",s); cards[i]=s[0];//读入牌库的牌 } top=1;//指针指向牌库的第一张牌 rungame();//运行游戏(在下一部分) if (pig[1].hp<=0) printf("FP\n"); else printf("MP\n");//获胜者输出 for (int i=1;i<=n;++i){//依次输出每个人的牌 if (pig[i].hp<=0) printf("DEAD\n"); else{ printcard(i); } } return 0; } ``` ### $Part5:$游戏的运行 由于所有部分都已经讲完了,所以直接上代码。 ``` void rungame(){ while (true){//游戏一直运行下去 for (int i=1;i<=n;++i){ if (gameover) return;//实时准备游戏结束 if (pig[i].hp<=0) continue;//猪死了就直接跳过 getcard(i);getcard(i);//摸牌 bool use_k=true;//表示这头猪是否出过杀 for (int j=1;j<=pig[i].cnt;++j){ if (pig[i].card[j]=='X') continue;//空牌,直接跳过 if (gameover) return; switch (pig[i].card[j]){ case 'P':{ if (pig[i].hp<4){//如果能吃桃就吃 pig[i].card[j]='X'; pig[i].hp++; } break; } case 'K':{ if (use_k && enemy(i,pig[i].nxt)){//如果和后面的一只猪是敌人 pig[i].card[j]='X'; usecard_k(i,pig[i].nxt);//直接上 if (!pig[i].weapon){ use_k=false;//没有连弩的话就不能再出杀了 } j=0;//从头再来,因为整个场面可能发生改变 } break; } case 'F':{//决斗,相对来说难实现一些 if (pig[i].id==3){//如果是反猪,目标就是主猪 pig[i].card[j]='X'; getenemy(i,1);//和主猪表敌意 if (usecard_j(i,1,0)) break;//如果被无懈可击挡住的话就退出 usecard_f(i,1); j=0; }else{ int to=pig[i].nxt;//还是逆时针循环找最近的敌对的猪 while (true){ if (enemy(i,to)){ pig[i].card[j]='X'; getenemy(i,to); if (usecard_j(i,to,0)) break; usecard_f(i,to); j=0; break; } to=pig[to].nxt; if (to==i) break;//转完一圈就退出 } } break; } case 'N':{//南猪入侵和万箭齐发只要调用函数就行了 pig[i].card[j]='X'; usecard_n(i,pig[i].nxt); j=0; break; } case 'W':{ pig[i].card[j]='X'; usecard_w(i,pig[i].nxt); j=0; break; } case 'Z':{//装备连弩 pig[i].card[j]='X'; pig[i].weapon=true; use_k=true; j=0; break; } } while (pig[i].card[pig[i].cnt]=='X') pig[i].cnt--;//一个优化,清掉最后没有的牌 } } } } ``` ### $Part6:$完整代码 ``` #include <bits/stdc++.h> using namespace std; #define N 11 #define M 2222 struct player{ int id; int hp; int cnt; char card[M]; int pre; int nxt; int pigid; bool special; bool weapon; }pig[N]; int n,m; int top; char cards[M]; bool gameover; int FPcnt; void printcard(int i){ bool first=true; for (int j=1;j<=pig[i].cnt;++j){ if (pig[i].card[j]=='X') continue; if (!first){ printf(" "); }else first=false; printf("%c",pig[i].card[j]); } printf("\n"); } void getcard(int i){ if (top>m) top=m; pig[i].cnt++; pig[i].card[pig[i].cnt]=cards[top]; top++; } int findcard(int x,char card){ for (int i=1;i<=pig[x].cnt;++i){ if (pig[x].card[i]==card){ pig[x].card[i]='X'; return true; } } return false; } void getfriend(int x,int y){ if (pig[y].pigid==0) return; if (pig[y].pigid!=3) pig[x].special=false,pig[x].pigid=1; if (pig[y].pigid==3) pig[x].special=true,pig[x].pigid=3; } void getenemy(int x,int y){ if (pig[y].pigid==0) return; if (pig[y].pigid!=3) pig[x].pigid=3,pig[x].special=true; if (pig[y].pigid==3) pig[x].pigid=1,pig[x].special=false; } void dechp(int x,int i){ pig[i].hp--; while (pig[i].hp<=0 && findcard(i,'P')){ pig[i].hp++; } if (pig[i].hp<=0){ pig[i].cnt=0; pig[i].weapon=false; if (pig[i].id==1){ gameover=true; return; }else if (pig[i].id==2){ if (pig[x].id==1){ pig[x].cnt=0; pig[x].weapon=false; } }else if (pig[i].id==3){ FPcnt--; if (FPcnt==0){ gameover=true; return; } getcard(x);getcard(x);getcard(x); } pig[pig[i].pre].nxt=pig[i].nxt; pig[pig[i].nxt].pre=pig[i].pre; } } bool enemy(int x,int y){ if (pig[x].id==1 && pig[y].special==true) return true; if (pig[y].pigid==0) return false; if (pig[x].id!=3 && pig[y].pigid==3) return true; if (pig[x].id==3 && pig[y].pigid!=3) return true; return false; } bool friendly(int x,int y){ if (pig[y].pigid==0) return false; if (pig[x].id!=3 && pig[y].pigid!=3) return true; if (pig[x].id==3 && pig[y].pigid==3) return true; return false; } bool usecard_j(int from,int to,int last){ int now=from; while (true){ if ((last==0 && friendly(now,to)) || (last!=0 && enemy(now,last))){ if (findcard(now,'J')){ if (last==0 && friendly(now,to)) getfriend(now,to); if (last!=0 && enemy(now,last)) getenemy(now,last); if (!usecard_j(pig[now].nxt,to,now)){ return true; } } } now=pig[now].nxt; if (now==from) return false; } } void usecard_k(int from,int to){ getenemy(from,to); bool p=findcard(to,'D'); if (p==false){ dechp(from,to); } } void usecard_f(int from,int to){ if (pig[from].id==1 && pig[to].id==2){ dechp(from,to); return; } while (true){ bool flag1=findcard(to,'K'); if (!flag1){ dechp(from,to); return; } bool flag2=findcard(from,'K'); if (!flag2){ dechp(to,from); return; } } } void usecard_n(int from,int to){ while (true){ if (gameover) return; if (!usecard_j(from,to,0)){ bool flag=findcard(to,'K'); if (!flag){ if (pig[to].id==1 && pig[from].pigid==0){ pig[from].special=true; } dechp(from,to); } } to=pig[to].nxt; if (to==from) return; } } void usecard_w(int from,int to){ while (true){ if (gameover) return; if (!usecard_j(from,to,0)){ bool flag=findcard(to,'D'); if (!flag){ if (pig[to].id==1 && pig[from].pigid==0){ pig[from].special=true; } dechp(from,to); } } to=pig[to].nxt; if (to==from) return; } } void rungame(){ while (true){ for (int i=1;i<=n;++i){ if (gameover) return; if (pig[i].hp<=0) continue; getcard(i);getcard(i); bool use_k=true; for (int j=1;j<=pig[i].cnt;++j){ if (pig[i].card[j]=='X') continue; if (gameover) return; switch (pig[i].card[j]){ case 'P':{ if (pig[i].hp<4){ pig[i].card[j]='X'; pig[i].hp++; } break; } case 'K':{ if (use_k && enemy(i,pig[i].nxt)){ pig[i].card[j]='X'; usecard_k(i,pig[i].nxt); if (!pig[i].weapon){ use_k=false; } j=0; } break; } case 'F':{ if (pig[i].id==3){ pig[i].card[j]='X'; getenemy(i,1); if (usecard_j(i,1,0)) break; usecard_f(i,1); j=0; }else{ int to=pig[i].nxt; while (true){ if (enemy(i,to)){ pig[i].card[j]='X'; getenemy(i,to); if (usecard_j(i,to,0)) break; usecard_f(i,to); j=0; break; } to=pig[to].nxt; if (to==i) break; } } break; } case 'N':{ pig[i].card[j]='X'; usecard_n(i,pig[i].nxt); j=0; break; } case 'W':{ pig[i].card[j]='X'; usecard_w(i,pig[i].nxt); j=0; break; } case 'Z':{ pig[i].card[j]='X'; pig[i].weapon=true; use_k=true; j=0; break; } } while (pig[i].card[pig[i].cnt]=='X') pig[i].cnt--; } } } } int main(){ scanf("%d%d",&n,&m); for (int i=1;i<N;++i){ for (int j=1;j<M;++j){ pig[i].card[j]='X'; } pig[i].hp=4; } for (int i=1;i<=n;++i){ char names[5],s[5]; scanf("%s",names); for (int j=1;j<=4;++j){ scanf("%s",s); pig[i].card[j]=s[0]; } pig[i].cnt=4; if (names[0]=='M') pig[i].id=1,pig[i].pigid=1; else if (names[0]=='Z') pig[i].id=2; else pig[i].id=3,FPcnt++; pig[i].nxt=i+1; pig[i].pre=i-1; } pig[n].nxt=1; pig[1].pre=n; for (int i=1;i<=m;++i){ char s[5]; scanf("%s",s); cards[i]=s[0]; } top=1; rungame(); if (pig[1].hp<=0) printf("FP\n"); else printf("MP\n"); for (int i=1;i<=n;++i){ if (pig[i].hp<=0) printf("DEAD\n"); else{ printcard(i); } } return 0; } ```