【BZOJ1972】[SDOI2010] 猪国杀(恶心的大模拟)

时间:2022-04-16 13:48:20

点此看题面

大致题意: 让你模拟一个游戏猪国杀的过程。

几大坑点

对于这种模拟题,具体思路就不讲了,就说说有哪些坑点。

  • 题面有锅,反猪是\(FP\)。

  • 数据有锅,牌堆中的牌可能不够用,牌堆为空之后需一直抽最后一张牌。

  • 主猪杀死忠猪后猪哥连弩也要清除

  • 无懈可击也可以用无懈可击抵消

  • 使用决斗的猪可能死亡。

  • 无懈可击是从使用锦囊牌的猪开始轮流选择是否响应。

  • 使用完一张牌后(不包括桃)有可能会导致之前跳过的杀或决斗有对象使用,因此要重新扫描一遍。

  • 只有主猪会特别针对类反猪

  • 如果杀死某只反猪后游戏结束,是不摸三张牌的。

大致就是这些了,我还犯了一些其他十分智障的错误,以至于调到绝望。

代码

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define uint unsigned int
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define abs(x) ((x)<0?-(x):(x))
#define INF 1e9
#define Inc(x,y) ((x+=(y))>=MOD&&(x-=MOD))
#define ten(x) (((x)<<3)+((x)<<1))
#define hl_AK_NOI true
#define GetCard(x) (s[x].card[++s[x].k]=cards[cd<m?++cd:m])//摸一张牌,注意牌堆中的牌可能不够用
using namespace std;
int n,m,cd=0,Last[15],Next[15];
char cards[2005];
class FIO
{
private:
#define Fsize 100000
#define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
#define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,FoutSize,stdout),Fout[(FoutSize=0)++]=ch))
int f,FoutSize,OutputTop;char ch,Fin[Fsize],*FinNow,*FinEnd,Fout[Fsize],OutputStack[Fsize];
public:
FIO() {FinNow=FinEnd=Fin;}
inline void read(int &x) {x=0,f=1;while(!isdigit(ch=tc())) f=ch^'-'?1:-1;while(x=ten(x)+(ch&15),isdigit(ch=tc()));x*=f;}
inline void read_char(char &x) {while(isspace(x=tc()));}
inline void read_string(string &x) {x="";while(isspace(ch=tc()));while(x+=ch,!isspace(ch=tc())) if(!~ch) return;}
inline void write(int x) {if(!x) return (void)pc('0');if(x<0) pc('-'),x=-x;while(x) OutputStack[++OutputTop]=x%10+48,x/=10;while(OutputTop) pc(OutputStack[OutputTop]),--OutputTop;}
inline void write_char(char x) {pc(x);}
inline void write_string(string x) {register int i,len=x.length();for(i=0;i<len;++i) pc(x[i]);}
inline void end() {fwrite(Fout,1,FoutSize,stdout);}
}F;
struct Pig//定义结构体,表示一只猪
{
int id,Type,hp,status,k;//id记录猪的编号,Type记录猪的类型,hp记录猪的剩余血量,status记录猪当前表明的状态(0:未知;1:主猪;2:忠猪;3:反猪;4:类反猪),k记录手中牌的数量
char card[5005];//card存储手牌
bool dead,Z;//dead记录猪是否死亡,Z记录猪是否有猪哥连弩
Pig(int x=0,string y="NULL"):id(x),Type(y[0]^'M'?(y[0]^'F'?2:3):1)//构造函数
{
if(y=="NULL") return;//一开始很智障地没写这句话,结果炸飞
status=(Type==1),hp=k=4,dead=Z=false;
register int i;
for(i=1;i<=4;++i) F.read_char(card[i]);//读入手牌
for(i=5;i<=5000;++i) card[i]='\0';
}
inline void Hurt(int);//受到伤害
inline bool Use(char ch)//查询是否有这张牌,如果有就使用并返回true,否则返回false
{
for(register int i=1;i<=k;++i) if(card[i]==ch) return card[i]='\0',true;//扫描每一张牌,查询是否有这张牌
return false;//返回false
}
}s[15];
inline void End()//游戏结束
{
register int i,j;
for(F.write_string(s[1].dead?"FP\n":"MP\n"),i=1;i<=n;++i)
{
if(s[i].dead) {F.write_string("DEAD\n");continue;}//如果死亡输出DEAD
for(j=1;j<=s[i].k;++j) if(s[i].card[j]) F.write_char(s[i].card[j]),F.write_char(' ');//输出手牌
F.write_char('\n');
}
F.end(),exit(0);
}
inline int GetLast(int x) {return s[Last[x]].dead?Last[x]=GetLast(Last[x]):Last[x];}//查询前一头猪
inline int GetNext(int x) {return s[Next[x]].dead?Next[x]=GetNext(Next[x]):Next[x];}//查询后一头猪
inline void Pig::Hurt(int x)//受到来源于编号为x的猪的一点伤害
{
if(--hp) return;//如果没死,退出函数
if(Use('P')) return (void)(++hp);//如果有桃,回一点体力
k=0,dead=true;//清空手牌,标记已死亡
if(Type==1) End();//如果死掉的是主猪,说明游戏结束了
if(Type==2&&s[x].Type==1) s[x].k=0,s[x].Z=false;//如果是主猪杀死了忠猪,清空主猪手牌,并注意清除猪哥连弩
Last[GetNext(id)]=Last[id],Next[GetLast(id)]=Next[id];//将该猪从牌局中删去
if(Type^3) return;//如果死的不是反猪,退出函数
register int i,t,FP=0;
for(i=1,t=0;i^t;i=GetNext(i),t=1) if(s[i].Type==3) {FP=true;break;}//判断是否还有反猪
if(!FP) End();//如果没有反猪,说明游戏结束了
GetCard(x),GetCard(x),GetCard(x);//摸三张牌
}
inline int GetStatus(int x,int y)//判断x与y的关系(1:同伙;2:没有关系;3:敌人)
{
if(!s[y].status) return 2;//如果y还没表明身份,返回2
switch(s[x].Type)//分类讨论(注意除主猪外其他猪与类反猪没有关系)
{
case 1:if(s[y].status==1) return 1;if(s[y].status==2) return 1;if(s[y].status==3) return 3;if(s[y].status==4) return 3;break;
case 2:if(s[y].status==1) return 1;if(s[y].status==2) return 1;if(s[y].status==3) return 3;if(s[y].status==4) return 2;break;
case 3:if(s[y].status==1) return 3;if(s[y].status==2) return 3;if(s[y].status==3) return 1;if(s[y].status==4) return 2;break;
}
return 2;
}
inline void BeGood(int x,int y)//通过x向y献殷勤,更新x的身份
{
if((s[y].status==1||s[y].status==2)&&s[x].Type^1) s[x].status=2;//如果y是忠猪或反猪且x不是主猪,说明x是忠猪
if(s[y].status==3) s[x].status=3;//如果y是反猪,说明x是反猪
}
inline void BeBad(int x,int y)//通过x向y表敌意,更新x的身份
{
if(s[y].status==1||s[y].status==2) s[x].status=3;//如果y是忠猪或反猪,说明x是反猪
if(s[y].status==3&&s[x].Type^1) s[x].status=2;//如果y是反猪且x不是主猪,说明x是忠猪
}
inline void K(int x,int y)//x向y打出一张杀
{
BeBad(x,y);//x向y表敌意了
if(!s[y].Use('D')) s[y].Hurt(x);//如果y没有出闪,就受到来自x的一点伤害
}
inline bool J(int x,int y,int v)//依次决定对于由x对y打出的一张无懈可击是否相应,其中v表示出无懈可击是献殷勤还是表敌意(1:献殷勤;3:表敌意)
{
register int i,t;
for(i=x,t=0;i^t;i=GetNext(i),t=x)
{
if(GetStatus(i,y)^v) continue;//如果不是该关系,就跳过
if(s[i].Use('J'))//如果打出无懈可击
{
v^1?BeBad(i,y):BeGood(i,y);
return !J(i,y,4-v);//递归调用该函数
}
}
return false;//没有猪打出无懈可击
}
inline void Fight(int x,int y)//x向y打出决斗
{
register int i1=1,i2=1;
BeBad(x,y);//x向y表敌意
if(J(x,y,1)) return;//如果有猪打出无懈可击,退出函数
if(s[x].Type==1&&s[y].Type==2) return (void)(s[y].Hurt(x));//如果x为主猪,y为忠猪,则必定是y受到伤害
while(hl_AK_NOI)//两猪轮流出杀
{
if(!s[y].Use('K')) return (void)(s[y].Hurt(x));//如果y没杀了,受到来自x的一点伤害
if(!s[x].Use('K')) return (void)(s[x].Hurt(y));//如果x没杀了,受到来自y的一点伤害
}
}
inline void AOE(int x,char Need)//由x发起的一次范围攻击,Need表示需要打出的牌
{
register int i;
for(i=GetNext(x);i^x;i=GetNext(i))
{
if(J(x,i,1)||s[i].Use(Need)) continue;//如果有猪打出无懈可击,或该猪打出了需要打出的牌,跳过
if(s[i].Type==1&&!s[x].status) s[x].status=4;//如果受到伤害的是主猪,且打出范围攻击的猪未表明身份,则标记其为类反猪
s[i].Hurt(x);//当前猪受到来自x的一点伤害
}
}
inline void Work()//出牌
{
register int i,j,kk=0,did=0,used;register char op;static int nw=1;
for(GetCard(nw),GetCard(nw),i=1;i<=s[nw].k;++i)//摸两张牌
{
op=s[nw].card[i],s[nw].card[i]='\0';
switch(op)
{
case 'K':
if(did&&!s[nw].Z) {s[nw].card[++kk]='K';break;}//如果出过杀,且没有猪哥连弩,跳过
if(GetStatus(nw,GetNext(nw))==3) K(nw,GetNext(nw)),did=1,i=kk=0;//如果与下一头猪是敌对关系,则打出杀,并从头开始扫描
else s[nw].card[++kk]='K';
break;
case 'F':
if(s[nw].Type==3) {Fight(nw,1),i=kk=0;break;}//如果是反猪,则对主猪打出决斗,并从头开始扫描
for(used=0,j=GetNext(nw);j^nw;j=GetNext(j)) if(GetStatus(nw,j)==3) {Fight(nw,j),used=true,i=kk=0;break;}//找到一个敌对关系的猪,打出决斗,并标记已使用,然后从头开始扫描
!used&&(s[nw].card[++kk]='F');
break;
case 'D':s[nw].card[++kk]='D';break;
case 'J':s[nw].card[++kk]='J';break;
case 'N':AOE(nw,'K'),i=kk=0;break;//打出南猪入侵,并从头开始扫描
case 'W':AOE(nw,'D'),i=kk=0;break;//打出万箭齐发,并从头开始扫描
case 'P':if(s[nw].hp<4) ++s[nw].hp;else s[nw].card[++kk]='P';break;//如果未满血,回复一点体力
case 'Z':s[nw].Z=true,i=kk=0;break;//装备猪哥连弩,然后从头开始扫描
}
if(!s[nw].k) kk=0;
}
s[nw].k=kk,nw=GetNext(nw);
}
int main()
{
register int i;register string st;
for(F.read(n),F.read(m),i=1;i<=n;++i) F.read_string(st),s[i]=Pig(i,st),Last[i]=i-1,Next[i]=i+1;//读入每一头猪的信息,并初始化其前一头猪和后一头猪
for(Last[1]=n,Next[n]=1,i=1;i<=m;++i) F.read_char(cards[i]);//读入牌堆
while(hl_AK_NOI) Work();//一直操作
return F.end(),0;
}