这道题我认为非常有意义
登录后可以回复该讨论。
《猪国杀》是一种多猪牌类回合制游戏,一共有 种角色:主猪,忠猪,反猪。每局游戏主猪有且只有 只,忠猪和反猪可以有多只,每只猪扮演 种角色。
主猪 / :自己存活的情况下消灭所有的反猪。
忠猪 / :不惜一切保护主猪,胜利条件与主猪相同。
反猪 / :杀死主猪。
游戏开始时,每个玩家手里都会有 张牌,且体力上限和初始体力都是 。
开始游戏时,从主猪开始,按照逆时针方向(数据中就是按照编号从 的顺序)依次行动。
每个玩家自己的回合可以分为 2 个阶段:
每张手牌用 个字母表示,字母代表牌的种类。
『桃 / 』在自己的回合内,如果自己的体力值不等于体力上限,那么使用 个桃可以为自己补充 点体力,否则不能使用桃;桃只能对自己使用;在自己的回合外,如果自己的体力变为 或者更低,那么也可以使用。
『杀 / 』在自己的回合内,对攻击范围内除自己以外的 名角色使用。如果没有被『闪』抵消,则造成 点伤害。无论有无武器,杀的攻击范围都是 。
『闪 / 』当你受到杀的攻击时,可以弃置 张闪来抵消杀的效果。
『决斗 / 』出牌阶段,对除自己以外任意 名角色使用,由目标角色先开始,自己和目标角色轮流弃置 张杀,首先没有杀可弃的一方受到 点伤害,另一方视为此伤害的来源。
『南猪入侵 / 』出牌阶段,对除你以外所有角色使用,按逆时针顺序从使用者下家开始依次结算,除非弃置 张杀,否则受到 点伤害。
『万箭齐发 / 』和南猪入侵类似,不过要弃置的不是杀而是闪。
『无懈可击 / 』在目标锦囊生效前抵消其效果。每次有 张锦囊即将生效时,从使用这张锦囊的猪开始,按照逆时针顺序,依次得到使用无懈可击的机会;效果:用于决斗时,决斗无效并弃置;用于南猪入侵或万箭齐发时,当结算到某个角色时才能使用,当前角色不需弃置牌并且不会受到伤害(仅对 个角色产生效果);用于无懈可击时,成为目标的无懈可击被无效。
伤害来源:杀、南猪入侵、万箭齐发的伤害来源均是使用该牌的猪,决斗的伤害来源如上;
距离:两只猪的距离定义为沿着逆时针方向间隔的猪数 。即初始时 和 的距离为 ,但是 和 的距离就是 。注意一个角色的死亡会导致一些猪距离的改变;
玩家死亡:如果该玩家的体力降到 或者更低,并且自己手中没有足够的桃使得自己的体力值回到 ,那么就死亡了,死亡后所有的牌(装备区,手牌区)被弃置;
奖励与惩罚:反猪死亡时,最后一个伤害来源处(即使是反猪)立即摸 张牌。忠猪死亡时,如果最后一个伤害来源是主猪,那么主猪所有装备牌、手牌被弃置。
注意:一旦达成胜利条件,游戏立刻结束,因此即使会摸 张牌或者还有牌可以用也不用执行了。
现在,我们已经知道每只猪的角色、手牌,还有牌堆初始情况,并且假设每个角色会按照如下的行为准则进行游戏,你需要做的就是告诉小猪 iPig 最后的结果。
注意:主猪的身份一开始就公开了;忠猪不会跳反,反猪也不会跳忠;不管是忠猪还是反猪,能够跳必然跳。
限于 iPig 只会用 P++ 语言写 A + B,他请你用 Pigcal (Pascal)、P (C) 或 P++ (C++) 语言来帮他预测最后的结果。
输入文件第一行包含两个正整数 和 ,分别代表玩家数和牌堆中牌的数量。数据保证牌的数量够用。
接下来 行,每行 个字符串,依次表示对第 只猪的角色和初始 张手牌描述。编号为 的肯定是主猪。
再接下来一行,一共 个字符串,按照从牌堆顶部到牌堆底部的顺序描述每张牌。
注意:所有的相邻的两个字符串都严格用 个空格隔开,行尾没有多余空格。
输出数据第一行包含一个字符串代表游戏结果。如果是主猪胜利,那么输出 ,否则输出 。数据保证游戏总会结束。
接下来 行,第 行是对第 只猪的手牌描述(注意只需要输出手牌),按照手牌从左往右的顺序输出,相邻两张牌用 个空格隔开,行末尾没有多余空格。如果这只猪已阵亡,那么只要输出 即可。
注意:如果要输出手牌而没有手牌的话,那么只需输出 个空行。
由于数据问题,若牌堆已空,按照每次抽牌抽到的都是最后一张。
3 10
MP D D F F
ZP N N N D
FP J J J J
F F D D J J F F K D
FP
DEAD
DEAD
J J J J J J D
第一回合:
下一回合:
一共 组测试数据,每个点 分。
的数据没有锦囊牌,另外 的数据没有无懈可击。
1 篇题解
登录后即可使用 Markdown 发布题解。
登录 / 注册Author: Yuyi
这道题看起来像一局复杂的卡牌游戏,但题目已经规定了每只猪在任何情况下的行动准则,因此我们不需要搜索最优策略,也不需要做博弈论。
我们真正要做的是:
按照题目给定的优先级,完整、准确地重放整局游戏,直到一方满足胜利条件。
游戏中的每一个选择实际上都是确定的:
所以本题的核心不是算法复杂度,而是把题面中的每一条规则翻译成状态和函数,并保证事件发生的先后顺序完全正确。
每只猪都有两个不同的“身份”。
sf[i]真实身份在整局游戏中不会改变:
sf[i] | 真实身份 |
|---|---|
主猪 MP | |
忠猪 ZP | |
反猪 FP |
真实身份决定:
j[i]其他猪做决策时,不能直接根据 sf[i] 看穿身份,而要根据它已经做出的行为判断。因此还需要记录公开状态:
j[i] | 含义 |
|---|---|
| 尚未跳身份 | |
| 已跳忠 | |
| 已跳反 | |
| 被主猪视为类反猪 |
主猪的身份开局公开,所以初始化时令
j[1] = 1;
注意,类反猪并不是真的跳反,它只代表主猪的怀疑。把它统一存在 j[i] 中没有问题,因为代码中只有主猪选择攻击目标时会把 j[i] == 3 当成敌人,忠猪和反猪都不会据此行动。
当一只忠猪或反猪通过「杀」「决斗」或「无懈可击」真正跳明身份后,直接覆盖原来的类反状态:
void jp(int p) {
if (sf[p] == 2) j[p] = 1;
if (sf[p] == 3) j[p] = 2;
}
这正好对应题目中的“如果之后跳了,那么主猪会重新认识这只猪”。
| 变量 | 含义 |
|---|---|
n | 猪的数量 |
m | 初始牌堆中的牌数 |
d | 牌堆,队首是当前牌堆顶 |
cd[i] | 第 只猪按从左到右顺序保存的手牌 |
cnt[i][c] | 第 只猪手中编号为 的牌有多少张 |
sf[i] | 第 只猪的真实身份 |
j[i] | 第 只猪当前公开出来的身份状态 |
bld[i] | 第 只猪当前的体力值 |
f_bld | 所有仍存活反猪的体力值之和 |
ak[i] | 第 只猪是否装备猪哥连弩 |
alive[i] | 第 只猪是否存活 |
其中最值得解释的是 cd 与 cnt 为什么要同时存在。
cd[i] 负责维护手牌顺序,因为主动出牌时必须找到最靠左的可用牌;cnt[i][c] 负责快速判断某种牌是否存在,例如受到「杀」时,只需判断:
if (cnt[q][3])
二者始终满足不变量
因此,每次摸牌、用牌和弃光手牌时,都必须同步修改这两个结构。
原代码开头还有一些竞赛模板中的通用定义:ll、inf、eps、dep、lowbit、maxn、mo、pi 等。它们并没有参与本题模拟,可以安全删除;rep 只是循环宏,read() 只是整数快读。为了让最终代码更清楚,文末版本删除了无关模板量,并统一使用 cin。
为了方便使用数组统计,将八种牌映射为整数:
| 编号 | 字符 | 牌 |
|---|---|---|
P | 桃 | |
K | 杀 | |
D | 闪 | |
F | 决斗 | |
N | 南猪入侵 | |
W | 万箭齐发 | |
J | 无懈可击 | |
Z | 猪哥连弩 |
check_cd() 完成字符到编号的转换,back_cd() 在最后输出时完成反向转换。
| 函数 | 职责 |
|---|---|
check_cd() | 把牌的字符转成 的编号 |
back_cd() | 把牌的编号转回输出字符 |
use() | 使用并删除最靠左的一张指定牌 |
game_end() | 输出胜方及所有猪的最终手牌并结束程序 |
gave() | 从牌堆顶摸指定数量的牌 |
heal() | 回复一点体力,并同步反猪总血量 |
save() | 濒死时尝试使用桃自救 |
hurt() | 扣血,并处理自救、死亡、胜负、奖励和惩罚 |
get_next() | 找到下一只存活的猪 |
attack() | 按身份和牌种选择攻击目标 |
jp() | 在表敌意后跳明真实阵营 |
kill_player() | 结算「杀」与「闪」 |
wx() | 递归结算无懈可击链 |
nm() | 结算南猪入侵 |
wj() | 结算万箭齐发 |
jd() | 结算决斗 |
solve() | 初始化并执行整局游戏的回合循环 |
f_bld 为什么记录反猪总血量代码维护
每当反猪受到一点伤害,就令 f_bld--;每当反猪吃桃,就令 f_bld++。
由于每次伤害都是 点,而且体力降到 后会立刻求桃或死亡,所以每只活反猪的体力一定为正。于是:
这样便可以在反猪死亡后用 的时间判断主猪阵营是否获胜。
use(p, cid) 做两件事:
cnt[p][cid] 减一;cd[p] 中删除最靠左的一张 cid。void use(int p, int cid) {
--cnt[p][cid];
for (int i = 0; i < (int)cd[p].size(); ++i) {
if (cd[p][i] == cid) {
cd[p].erase(cd[p].begin() + i);
break;
}
}
}
主动出牌时,外层已经从左到右找到了第一个可用牌种;同种牌在当前状态下的可用性完全相同,所以再删除最靠左的一张该种牌就是正确的。
gave(p, x) 表示第 只猪连续摸 张牌。
题目规定牌堆空后,每次都视为摸到最后一张牌。因此牌堆中只剩一张牌时,不能再把它弹出:
if (d.size() > 1) d.pop_front();
这样最后一张牌会永远留在队首,之后每次摸牌都会得到它。
猪的行动顺序是
死亡的猪会被跳过。get_next(p) 从 的下家开始寻找第一只存活的猪,它就是与 距离为 的猪。
int get_next(int p) {
int curr = p % n + 1;
while (!alive[curr]) curr = curr % n + 1;
return curr;
}
attack(p, c) 统一寻找攻击目标,其中:
c == 0:使用「杀」,只能检查下一只存活的猪;c == 1:使用「决斗」,可以绕场寻找第一个合法目标。目标规则如下:
| 使用者 | 「杀」的目标 | 「决斗」的目标 |
|---|---|---|
| 主猪 | 距离 且为跳反或类反 | 顺序第一只跳反或类反 |
| 忠猪 | 距离 且为跳反 | 顺序第一只跳反 |
| 反猪 | 优先距离 的主猪,否则距离 的跳忠 | 主猪 |
为什么反猪使用「决斗」时可以直接返回主猪?因为「决斗」没有距离限制,只要主猪仍存活就一定可以选择主猪;而主猪死亡时游戏已经立刻结束,不会继续模拟。
只要一只忠猪或反猪成功找到合法目标并打出「杀」或「决斗」,它就在表敌意,因此应立刻调用 jp(p)。
跳身份发生在牌打出时,而不是伤害生效时。即使「决斗」随后被无懈可击抵消,使用者也已经暴露身份。
使用无懈可能是在献殷勤,也可能是在表敌意,但无论属于哪一种,忠猪和反猪只要使用了它,就一定会跳身份。因此 wx() 在用掉 J 后也要立即更新 j[i]。
只有同时满足以下条件时,主猪才会把某只猪视为类反猪:
j[p] == 0;所以类反判断必须放在 hurt() 之后,并且只在主猪实际掉血的分支执行:
hurt(i, p);
if (sf[i] == 1 && j[p] == 0) j[p] = 3;
P自己的回合中,只要体力不足 且手中有桃,就把最靠左的桃吃掉。
濒死时,因为所有单次伤害都是 ,受伤前体力又一定大于 ,所以受伤后只可能恰好变成 。此时至多需要一张桃即可回到 点体力。
K 与闪 D找到合法目标后:
每个出牌阶段用 used_k 记录是否已经出过杀。满足
ak[i] || !used_k
时才允许继续出杀。装备猪哥连弩后 ak[i] = true,便不再受一次限制。
F先让目标出「杀」,然后双方交替出杀;第一个没有杀的一方受到对方造成的 点伤害。
唯一的特殊情况是:
主猪对真实身份为忠猪的目标发起决斗时,忠猪不会出杀。
代码直接让忠猪受伤:
if (sf[p] == 1 && sf[q] == 2) {
hurt(q, p);
return;
}
主猪之所以会攻击真实忠猪,只可能是该忠猪此前被误判成了类反猪。
N从使用者的下家开始,按环形顺序逐一结算每只其他存活角色:
W与南猪入侵完全相同,只需把响应牌从「杀」换成「闪」。
Z摸到后在出牌阶段必然装备。装备牌需要从手牌中移除,只在 ak[i] 中记录装备状态。
再次装备时旧武器被替换,但两把武器效果相同,所以仍然只需令
ak[i] = true;
即可。
定义:
bool wx(int p, int q, bool f)
三个参数分别表示:
p:原锦囊当前要作用的目标;q:本轮从哪只猪开始询问,初始时是锦囊使用者,递归时是上一张无懈的使用者;f:当前打出的无懈属于献殷勤还是表敌意。其中:
f == true:要抵消锦囊或上一层敌意无懈,相当于对目标献殷勤;f == false:要抵消上一层献殷勤无懈,相当于对目标表敌意。| 无懈使用者 | 愿意保护的目标 |
|---|---|
| 主猪、忠猪 | 主猪或已经跳忠的猪 |
| 反猪 | 已经跳反的猪 |
未跳身份的猪不能被献殷勤,包括使用者自己。
| 无懈使用者 | 愿意敌对的目标 |
|---|---|
| 主猪 | 跳反猪或类反猪 |
| 忠猪 | 跳反猪 |
| 反猪 | 主猪或跳忠猪 |
找到第一只必须使用无懈的猪 后:
J;因此代码是:
return !wx(p, i, !f);
如果后面无人再出无懈,递归返回 false,当前这张无懈就成功生效,因此取反后返回 true。
无懈数量与原锦囊是否生效的关系为:
| 无懈张数 | 原锦囊 |
|---|---|
| 偶数 | 生效 |
| 奇数 | 被抵消 |
递归中的每一层取反,恰好自动维护了这个奇偶关系。
特别注意:「南猪入侵」和「万箭齐发」不是整张牌只询问一次无懈,而是结算到每一只目标时,都要单独调用一次 wx()。
hurt(p, q) 表示 受到来自 的一点伤害。处理顺序必须是:
f_bld--;题目强调“一旦达成胜利条件,游戏立刻结束”,所以判断最后一只反猪死亡必须放在摸三张奖励牌之前。
每个出牌阶段都重复下面的过程:
重新扫描非常重要,因为一张牌的结算可能造成:
因此不能事先把“本回合要出的牌”一次性选出来。
对每一只仍存活的猪 :
used_k = false;外层不断循环,直到 game_end() 输出结果并立即结束程序。题目保证游戏一定结束。
下面证明该算法最终输出题目规定的唯一游戏结果。
摸牌时,算法同时把牌加入 cd[i] 末尾并增加对应的 cnt[i][c];用牌时,同时删除 cd[i] 中最靠左的该类牌并减少计数;弃光手牌时同时清空二者。因此 cd 始终保存准确的手牌顺序,cnt 始终保存准确的牌数。
出牌阶段每次都从 cd[i] 的最左端开始扫描,并在遇到第一张当前可用的牌时立即使用,随后重新扫描。因此不会跳过任何更靠左的可用牌,符合题目规则。
attack() 返回题目规定的攻击目标对于「杀」,算法只检查下一只存活角色,恰好对应距离 ;对于「决斗」,算法按照逆时针顺序检查所有其他角色。各身份判断目标时使用的公开状态,与主猪、忠猪、反猪的行动准则逐项一致。因此返回的一定是题目规定的第一个合法目标;无目标时返回 。
wx() 正确模拟整条无懈链每一层 wx() 都从当前锦囊或无懈的使用者开始,按逆时针顺序找到第一只按照行为准则必须使用无懈的猪。使用一张无懈后,下一层行为在献殷勤和表敌意之间切换,返回值同时取反。递归终点表示无人继续出无懈,所以逐层返回后,奇数张无懈抵消原效果,偶数张无懈保留原效果。故 wx() 与题面的无懈结算完全一致。
桃、杀、闪、决斗、南猪入侵、万箭齐发、无懈可击和猪哥连弩分别由对应函数按题面顺序处理;其中群体锦囊按座位顺序对每个目标独立结算,决斗从目标开始交替出杀,忠猪面对主猪决斗时直接放弃出杀。因此每一张牌造成的状态变化都与题目一致。
hurt() 在扣血后立刻处理桃、死亡和清牌,并在奖励与惩罚前优先判断主猪死亡或最后一只反猪死亡。因此所有死亡事件及其先后顺序都符合题意。
初始状态由输入准确建立。假设某个游戏事件前,算法状态与真实游戏状态一致;由引理 1 至引理 6,算法选出的下一个行动、目标、响应及其造成的全部状态变化都与真实游戏一致,因此下一事件前状态仍然一致。由数学归纳法,直到游戏结束,算法始终与题目规定的游戏过程一致,所以输出的胜方、存活状态和手牌顺序均正确。
这是一道过程模拟题,游戏长度由实际对局决定。设:
扫描手牌和从双端队列中删除牌最坏需要 ,寻找目标或寻找无懈使用者需要 ,因此可以写成:
空间复杂度为:
本题中 、初始牌堆大小 ,且题目保证游戏结束,直接模拟足以通过。
样例第一轮中:
下一轮:
FP。sf 是真实身份,j 是公开身份,二者绝对不能混用。Z。#include <bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
#define eps 1e-9
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define dep(i,a,b) for(int i=(a);i>=(b);i--)
#define lowbit(a) (a)&(-a)
using namespace std;
const int maxn = 2e3+5;
const int mo = 998244353;
const double pi = acos(-1.0);
template <typename T>
inline void read(T &X)
{
X = 0;int w = 0; char ch = 0;
while(!isdigit(ch)) {w |= ch == '-' ;ch = getchar();}
while(isdigit(ch)) X = X * 10 + (ch^48),ch = getchar();
if(w) X = -X;
}
int n,m;
deque<int> d; //牌堆
deque<int> cd[11]; //当前手牌
int sf[11]; //身份{主,忠,反}->{1,2,3}
int cnt[11][9]; //表示i人j牌有多少
//P-桃 K-杀 D-闪 F-决斗 N-南蛮 W-万箭 J-无懈 Z-连弩
//1-P 2-K 3-D 4-F 5-N 6-W 7-J 8-Z
int j[11]; //所跳身份{无,忠,反,类反}->{0,1,2,3}
int bld[11]; //血量
int f_bld; //反贼总血量
bool ak[11]; //是否有连弩
bool alive[11]; //是否活着
int check_cd(string str)
{
if(str == "P") return 1;
if(str == "K") return 2;
if(str == "D") return 3;
if(str == "F") return 4;
if(str == "N") return 5;
if(str == "W") return 6;
if(str == "J") return 7;
if(str == "Z") return 8;
return 0;
}
string back_cd(int cdd)
{
if(cdd == 1) return "P";
if(cdd == 2) return "K";
if(cdd == 3) return "D";
if(cdd == 4) return "F";
if(cdd == 5) return "N";
if(cdd == 6) return "W";
if(cdd == 7) return "J";
if(cdd == 8) return "Z";
return "";
}
void use(int p,int cid) //使用牌 {使用者,牌编号}
{
// cout << "a" << cid << " " << cnt[p][cid] << endl;
cnt[p][cid] --;
rep(i,0,(int)cd[p].size()-1) if(cd[p][i] == cid)
{
cd[p].erase(cd[p].begin() + i);
// cout << p << " " << cid << " " << j[p] << " " << bld[p] << " " << cd[p].size() << " " << alive[p] << endl;
break;
}
}
void game_end(bool f) //游戏结束 胜利方(0主忠,1反贼)
{
if(f) cout << "FP\n";
else cout << "MP\n";
rep(i,1,n)
{
if(!alive[i]) cout << "DEAD\n";
else
{
rep(k,0,(int)cd[i].size()-1)
{
if(k) cout << " ";
cout << back_cd(cd[i][k]);
}
cout << "\n";
}
}
exit(0);
}
void gave(int p,int x) //摸牌 {玩家,摸牌数}
{
rep(i,1,x)
{
cnt[p][d.front()] ++;
cd[p].push_back(d.front());
if(d.size() > 1) d.pop_front();
}
}
void heal(int p) //吃桃 玩家
{
bld[p] ++;
if(sf[p] == 3) f_bld ++;
return;
}
bool save(int p) //濒死 玩家
{
if(cnt[p][1])
{
use(p,1);
heal(p);
return 1; //脱离濒死
}
else return 0;
}
void hurt(int p,int q) //受伤+判断死亡 {受伤者,伤害来源}
{
bld[p] --;
if(sf[p] == 3) f_bld --;
if(bld[p] > 0) return;
if(save(p)) return;
alive[p] = 0;
cd[p].clear();
rep(i,1,8) cnt[p][i] = 0;
ak[p] = 0;
if(sf[p] == 1)
{
game_end(1);
}
if(sf[p] == 3)
{
if(f_bld == 0) game_end(0);
gave(q,3);
}
if(sf[p] == 2 && sf[q] == 1)
{
cd[q].clear();
rep(i,1,8) cnt[q][i] = 0;
ak[q] = 0;
}
}
int get_next(int p) //获得下一位玩家 当前玩家
{
int curr = p % n + 1;
while(!alive[curr])
{
curr = curr % n + 1;
}
return curr;
}
int attack(int p,int c) //判断攻击 {使用者,牌(0杀,1决斗)} 返回可攻击玩家
{
if(c)
{
if(sf[p] == 1)
{
rep(k,1,n-1)
{
int i = (p-1+k)%n+1;
if(alive[i] && (j[i] == 2 || j[i] == 3)) return i;
}
return 0;
}
if(sf[p] == 2)
{
rep(k,1,n-1)
{
int i = (p-1+k)%n+1;
if(alive[i] && j[i] == 2) return i;
}
return 0;
}
if(sf[p] == 3) return alive[1] ? 1 : 0;
}
else
{
int nxt = get_next(p);
if(sf[p] == 1)
{
if((j[nxt] == 2 || j[nxt] == 3) && alive[nxt]) return nxt;
return 0;
}
if(sf[p] == 2)
{
if(j[nxt] == 2) return nxt;
return 0;
}
if(sf[p] == 3)
{
if(sf[nxt] == 1 || j[nxt] == 1) return nxt;
return 0;
}
}
return 0;
}
void jp(int p) //使用杀或决斗后跳身份 玩家
{
if(sf[p] == 2) j[p] = 1;
if(sf[p] == 3) j[p] = 2;
}
void kill_player(int p,int q) //杀 {使用者,被锁定者}
{
if(cnt[q][3])
{
use(q,3);
return;
}
hurt(q,p);
return;
}
bool wx(int p,int q,bool f) //无懈可击 {被锁定者,原使用者,第一次?}
{
int i = q;
rep(k,1,n)
{
if(alive[i] && cnt[i][7])
{
bool want_use = 0;
if(f) //用无懈献殷勤:保护主/忠,或保护已经跳反的猪
{
if((sf[i] == 1 || sf[i] == 2) && (sf[p] == 1 || j[p] == 1)) want_use = 1;
if(sf[i] == 3 && j[p] == 2) want_use = 1;
}
else //用无懈表敌意:抵消对方阵营此前的献殷勤
{
if(sf[i] == 1 && (j[p] == 2 || j[p] == 3)) want_use = 1;
if(sf[i] == 2 && j[p] == 2) want_use = 1;
if(sf[i] == 3 && (sf[p] == 1 || j[p] == 1)) want_use = 1;
}
if(want_use)
{
use(i,7);
if(sf[i] == 2) j[i] = 1;
if(sf[i] == 3) j[i] = 2;
return !wx(p,i,!f);
}
}
i = i % n + 1;
}
return 0;
}
void nm(int p) //南蛮入侵 使用者
{
rep(i,p+1,n) if(alive[i])
{
if(wx(i,p,1)) continue;
if(cnt[i][2]) use(i,2);
else
{
hurt(i,p);
if(sf[i] == 1 && j[p] == 0) j[p] = 3;
}
}
rep(i,1,p-1) if(alive[i])
{
if(wx(i,p,1)) continue;
if(cnt[i][2]) use(i,2);
else
{
hurt(i,p);
if(sf[i] == 1 && j[p] == 0) j[p] = 3;
}
}
return;
}
void wj(int p) //万箭齐发 使用者
{
rep(i,p+1,n) if(alive[i])
{
if(wx(i,p,1)) continue;
if(cnt[i][3]) use(i,3);
else
{
hurt(i,p);
if(sf[i] == 1 && j[p] == 0) j[p] = 3;
}
}
rep(i,1,p-1) if(alive[i])
{
if(wx(i,p,1)) continue;
if(cnt[i][3]) use(i,3);
else
{
hurt(i,p);
if(sf[i] == 1 && j[p] == 0) j[p] = 3;
}
}
return;
}
void jd(int p,int q) //决斗 {使用者,被锁定者}
{
// cout << j[2] << endl;
if(wx(q,p,1)) return;
if(sf[p] == 1 && sf[q] == 2)
{
hurt(q,p);
// cout << attack(p,1) << " ";
// rep(i,0,cd[p].size()) cout << cd[p][i] << " ";
// cout << endl;
return;
}
else
{
while(true)
{
if(cnt[q][2]) use(q,2);
else
{
hurt(q,p);
return;
}
if(cnt[p][2]) use(p,2);
else
{
hurt(p,q);
return;
}
}
}
}
void solve()
{
read(n),read(m);
rep(i,1,n)
{
string s;
cin >> s;
bld[i] = 4;
alive[i] = 1;
if(s == "MP")
{
sf[i] = 1;
j[i] = 1;
}
if(s == "ZP") sf[i] = 2;
if(s == "FP")
{
sf[i] = 3;
f_bld += 4;
}
rep(j,1,4)
{
string ss;
cin >> ss;
int tmp = check_cd(ss);
cnt[i][tmp] ++;
cd[i].push_back(tmp);
}
}
rep(i,1,m)
{
string s;
cin >> s;
d.push_back(check_cd(s));
}
if(f_bld == 0) game_end(0);
while(true) //轮
{
rep(i,1,n) if(alive[i]) //回合
{
gave(i,2);
bool used_k = 0;
while(alive[i])
{
bool played = 0;
rep(cur,0,(int)cd[i].size()-1)
{
int now = cd[i][cur];
if(now == 1 && bld[i] < 4)
{
use(i,1);
heal(i);
played = 1;
break;
}
if(now == 5)
{
use(i,5);
nm(i);
played = 1;
break;
}
if(now == 6)
{
use(i,6);
wj(i);
played = 1;
break;
}
if(now == 8)
{
use(i,8);
ak[i] = 1;
played = 1;
break;
}
if(now == 2 && (ak[i] || !used_k))
{
int att = attack(i,0);
if(att)
{
use(i,2);
jp(i);
used_k = 1;
kill_player(i,att);
played = 1;
break;
}
}
if(now == 4)
{
int att = attack(i,1);
if(att)
{
use(i,4);
jp(i);
jd(i,att);
played = 1;
break;
}
}
}
if(!played) break;
}
}
}
return;
}
int main()
{
int T=1;
// freopen("mul.in","r",stdin);
// freopen("mul.out","w",stdout);
// read(T);
while (T--) solve();
return 0;
}
如果自己重新实现,建议按以下顺序逐层调试:
cnt 同步维护;这道题能否 AC,主要取决于状态是否分清、事件顺序是否严格,而不是使用了多高级的算法。
2 条讨论
登录后即可使用 Markdown 发起讨论和回复。
登录 / 注册这道题我认为非常有意义
登录后可以回复该讨论。
暂时没有回复。
登录后可以回复该讨论。