[luogu p2482] [SDOI2010]豬國殺
[SDOI2010]豬國殺
題目描述
遊戲背景
《豬國殺》是一種多豬牌類回合制遊戲,一共有 \(3\) 種角色:主豬,忠豬,反豬。每局遊戲主豬有且只有 \(1\) 只,忠豬和反豬可以有多隻,每隻豬扮演 $1 $ 種角色。
遊戲目的
主豬 / \(\texttt{MP}\):自己存活的情況下消滅所有的反豬。
忠豬 / \(\texttt{ZP}\):不惜一切保護主豬,勝利條件與主豬相同。
反豬 / \(\texttt{FP}\):殺死主豬。
遊戲過程
遊戲開始時,每個玩家手裡都會有 \(4\) 張牌,且體力上限和初始體力都是 \(4\) 。
開始遊戲時,從主豬開始,按照逆時針方向(資料中就是按照編號從 \(1 , 2, 3 \ldots n , 1 \ldots\)
每個玩家自己的回合可以分為 2 個階段:
- 摸牌階段:從牌堆頂部摸 \(2\) 張牌,依次放到手牌的最右邊;
- 出牌階段:你可以使用任意張牌,每次使用牌的時候都使用最靠左的能夠使用的牌。當然,要滿足如下規則:
- 如果沒有豬哥連弩,每個出牌階段只能使用 \(1\) 次「殺」來攻擊;
- 任何牌被使用後被棄置(武器是裝備上);被棄置的牌以後都不能再用,即與遊戲無關。
各種牌介紹
每張手牌用 \(1\) 個字母表示,字母代表牌的種類。
基本牌
- 『桃 / \(\texttt{P}\)』在自己的回合內,如果自己的體力值不等於體力上限,那麼使用 \(1\) 個桃可以為自己補充 \(1\)
- 『殺 / \(\texttt{K}\)』在自己的回合內,對攻擊範圍內除自己以外的 \(1\) 名角色使用。如果沒有被『閃』抵消,則造成 \(1\) 點傷害。無論有無武器,殺的攻擊範圍都是 \(1\)。
- 『閃 / \(\texttt{D}\)』當你受到殺的攻擊時,可以棄置 \(1\) 張閃來抵消殺的效果。
錦囊牌
- 『決鬥 / \(\texttt{F}\)』出牌階段,對除自己以外任意 \(1\) 名角色使用,由目標角色先開始,自己和目標角色輪流棄置 \(1\) 張殺,首先沒有殺可棄的一方受到 \(1\)
- 『南豬入侵 / \(\texttt{N}\)』出牌階段,對除你以外所有角色使用,按逆時針順序從使用者下家開始依次結算,除非棄置 \(1\) 張殺,否則受到 \(1\) 點傷害。
- 『萬箭齊發 / \(\texttt{W}\)』和南豬入侵類似,不過要棄置的不是殺而是閃。
- 『無懈可擊 / \(\texttt{J}\)』在目標錦囊生效前抵消其效果。每次有 \(1\) 張錦囊即將生效時,從使用這張錦囊的豬開始,按照逆時針順序,依次得到使用無懈可擊的機會;效果:用於決鬥時,決鬥無效並棄置;用於南豬入侵或萬箭齊發時,當結算到某個角色時才能使用,當前角色不需棄置牌並且不會受到傷害(僅對 \(1\) 個角色產生效果);用於無懈可擊時,成為目標的無懈可擊被無效。
裝備牌
- 『豬哥連弩 / \(\texttt{Z}\)』武器,攻擊範圍 \(1\) ,出牌階段你可以使用任意張殺; 同一時刻最多隻能裝 \(1\) 把武器;如果先前已經有了 \(1\) 把武器,那麼之後再裝武器的話,會棄置以前的武器來裝現在的武器。
特殊事件及概念解釋
- 傷害來源:殺、南豬入侵、萬箭齊發的傷害來源均是使用該牌的豬,決鬥的傷害來源如上;
- 距離:兩隻豬的距離定義為沿著逆時針方向間隔的豬數 \(+1\) 。即初始時 \(1\) 和 \(2\) 的距離為 \(1\) ,但是 \(2\) 和 \(1\) 的距離就是 \(n-1\) 。注意一個角色的死亡會導致一些豬距離的改變;
- 玩家死亡:如果該玩家的體力降到 \(0\) 或者更低,並且自己手中沒有足夠的桃使得自己的體力值回到 \(1\) ,那麼就死亡了,死亡後所有的牌(裝備區,手牌區)被棄置;
- 獎勵與懲罰:反豬死亡時,最後一個傷害來源處(即使是反豬)立即摸 \(3\) 張牌。忠豬死亡時,如果最後一個傷害來源是主豬,那麼主豬所有裝備牌、手牌被棄置。
注意:一旦達成勝利條件,遊戲立刻結束,因此即使會摸 \(3\) 張牌或者還有牌可以用也不用執行了。
現在,我們已經知道每隻豬的角色、手牌,還有牌堆初始情況,並且假設每個角色會按照如下的行為準則進行遊戲,你需要做的就是告訴小豬 iPig 最後的結果。
幾種行為
- 獻殷勤:使用無懈可擊擋下南豬入侵、萬箭齊發、決鬥;使用無懈可擊抵消表敵意;
- 表敵意:對某個角色使用殺、決鬥;使用無懈可擊抵消獻殷勤;
- 跳忠:即通過行動表示自己是忠豬。跳忠行動就是對主豬或對某隻已經跳忠的豬獻殷勤,或者對某隻已經跳反的豬表敵意;
- 跳反:即通過行動表示自己是反豬。跳反行動就是對主豬或對某隻已經跳忠的豬表敵意,或者對某隻已經跳反的豬獻殷勤。
注意:忠豬不會跳反,反豬也不會跳忠;不管是忠豬還是反豬,能夠跳必然跳。
行動準則
共性
- 每個角色如果手裡有桃且生命值未滿,那麼必然吃掉;
- 有南豬入侵、萬箭齊發、必然使用;有裝備必然裝上;
- 受到殺時,有閃必然棄置;
- 響應南豬入侵或者萬箭齊發時候,有殺 / 閃必然棄置;
- 不會對未表明身份的豬獻殷勤(包括自己)。
特性
- 主豬:
- 主豬會認為「沒有跳身份,且用南豬入侵 / 萬箭齊發對自己造成傷害的豬」是類反豬(沒傷害到不算,注意類反豬並沒有表明身份),如果之後跳了,那麼主豬會重新認識這隻豬;
- 對於每種表敵意的方式,對逆時針方向能夠執行到的第一隻類反豬或者已跳反豬表;如果沒有,那麼就不表敵意;
- 決鬥時會不遺餘力棄置殺;
- 如果能對已經跳忠的豬或自己獻殷勤,那麼一定獻;如果能夠對已經跳反的豬表敵意,那麼一定表。
- 忠豬:
- 對於每種表敵意的方式,對「逆時針方向能夠執行到的第一隻已經跳反的豬」表,如果沒有,那麼就不表敵意;
- 決鬥時,如果對方是主豬,那麼不會棄置殺,否則,會不遺餘力棄置殺;
- 如果有機會對主豬或者已經跳忠的豬獻殷勤,那麼一定獻。
- 反豬:
- 對於每種表敵意的方式,如果有機會則對主豬表,否則,對「逆時針方向能夠執行到的第一隻已經跳忠的豬」表,如果沒有,那麼就不表敵意;
- 決鬥時會不遺餘力棄置殺;
- 如果有機會對已經跳反的豬獻殷勤,那麼一定獻。
限於 iPig 只會用 P++ 語言寫 A + B,他請你用 Pigcal (Pascal)、P (C) 或 P++ (C++) 語言來幫他預測最後的結果。
輸入輸出格式
輸入格式
輸入檔案第一行包含兩個正整數 \(n\) \((2 \leqslant n \leqslant 10)\) 和 \(m\) \((m \leqslant 2000)\),分別代表玩家數和牌堆中牌的數量。資料保證牌的數量夠用。
接下來 \(n\) 行,每行 \(5\) 個字串,依次表示對第 \(i\) 只豬的角色和初始 \(4\) 張手牌描述。編號為 \(1\) 的肯定是主豬。 再接下來一行,一共 \(m\) 個字串,按照從牌堆頂部到牌堆底部的順序描述每張牌。
注意:所有的相鄰的兩個字串都嚴格用 \(1\) 個空格隔開,行尾沒有多餘空格。
輸出格式
輸出資料第一行包含一個字串代表遊戲結果。如果是主豬勝利,那麼輸出 \(\texttt{MP}\) ,否則輸出 \(\texttt{FP}\) 。資料保證遊戲總會結束。
接下來 \(n\) 行,第 \(i\) 行是對第 \(i\) 只豬的手牌描述(注意只需要輸出手牌),按照手牌從左往右的順序輸出,相鄰兩張牌用 \(1\) 個空格隔開,行末尾沒有多餘空格。如果這隻豬已陣亡,那麼只要輸出 \(\texttt{DEAD}\) 即可。
注意:如果要輸出手牌而沒有手牌的話,那麼只需輸出 \(1\) 個空行。
輸入輸出樣例
輸入樣例 #1
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
輸出樣例 #1
FP
DEAD
DEAD
J J J J J J D
說明
樣例解釋
第一回合:
- 主豬沒有目標可以表敵意;
- 接下來忠豬使用了 \(3\) 張南豬入侵,主豬掉了 \(3\) 點體力,並認為該角色為類反豬,\(3\) 號角色儘管手裡有無懈可擊,但是因為自己未表明身份,所以同樣不能對自己用,乖乖掉 \(3\) 點體力;
下一回合:
- 反豬無牌可出;
- 接下來主豬對著類反豬爆發,使用 \(4\) 張決鬥,忠豬死亡,結果主豬棄掉所有牌;
- 下來反豬摸到 \(1\) 張殺直接殺死主豬獲勝。
子任務
一共 \(20\) 組測試資料,每個點 \(5\) 分。
\(10\%\) 的資料沒有錦囊牌,另外 \(20\%\) 的資料沒有無懈可擊。
分析
最近太頹,逼自己刷一道大模擬。
這可以看做是一篇娛樂文章了吧……沒有題解。
況且這種大模擬沒啥好講的。
程式碼
/*
* @Author: crab-in-the-northeast
* @Date: 2020-09-08 20:12:24
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2020-09-09 22:47:29
*/
#include <iostream>
#include <cstdio>
#include <string>
const int maxm = 2005;
const int maxn = 15;
struct node {
int cardnum, health, next, last;
char identity, card[maxm];
bool Z;
}a[maxn];
char cards[maxm];
char identity[maxn];
int n, m;
int fpnum;
bool ended;
void GetCard(int idx) {
if (m == 0) m = 1;
a[idx].card[++a[idx].cardnum] = cards[m];
--m;
}
void all_kill(int killer, int deader) {
for (int i = 1; i <= a[deader].cardnum; ++i) {
if (a[deader].card[i] == 'P') {
a[deader].card[i] = 'X';
++a[deader].health;
return ;
}
}
a[a[deader].last].next = a[deader].next;
a[a[deader].next].last = a[deader].last;
if (deader == 1) {
ended = true;
return ;
}
if (a[deader].identity == 'F') --fpnum;
if (fpnum == 0) {
ended = true;
return ;
}
if (a[deader].identity == 'F') {
for (int i = 1; i <= 3; ++i)
GetCard(killer);
}
if (a[killer].identity == 'M' && a[deader].identity == 'Z') {
a[killer].cardnum = 0;
a[killer].Z = false;
}
}
void kill(int killer, int hurter) {
for (int i = 1; i <= a[hurter].cardnum; ++i) {
if (a[hurter].card[i] == 'D') {
a[hurter].card[i] = 'X';
return ;
}
}
--a[hurter].health;
if (a[hurter].health == 0) all_kill(killer, hurter);
}
bool NoMoreHurts(int x1, int x2, bool f) {
for (int i = x1; true; i = a[i].next) {
if (f) {
if (identity[x2] == a[i].identity || (identity[x2] == 'Z' && a[i].identity == 'M') || (identity[x2] == 'M' && a[i].identity == 'Z')) {
for (int j = 1; j <= a[i].cardnum; ++j) {
if (a[i].card[j] == 'J') {
a[i].card[j] = 'X';
identity[i] = a[i].identity;
return !NoMoreHurts(i, x1, false);
}
}
}
} else {
if (((a[i].identity == 'Z' || a[i].identity == 'M') && identity[x1] == 'F') || (a[i].identity == 'F' && (identity[x1] == 'Z' || identity[x1] == 'M'))) {
for (int j = 1; j <= a[i].cardnum; ++j) {
if (a[i].card[j] == 'J') {
a[i].card[j] = 'X';
identity[i] = a[i].identity;
return !NoMoreHurts(i, x1, false);
}
}
}
}
if (a[i].next == x1) break;
}
return false;
}
void NanZhuInvade(int x) {
for (int i = a[x].next; i != x; i = a[i].next) {
if (!NoMoreHurts(x, i, true)) {
int j;
for (j = 1; j <= a[i].cardnum; ++j) {
if (a[i].card[j] == 'K') {
a[i].card[j] = 'X';
break;
}
}
if (a[i].cardnum < j) {
--a[i].health;
if (i == 1 && identity[x] == 'X')
identity[x] = 'L';
if (a[i].health == 0)
all_kill(x, i);
if (ended) return ;
}
}
}
}
void battle(int x1, int x2) {
if (NoMoreHurts(x1, x2, true)) return ;
if (x1 == 1 && a[x2].identity == 'Z') {
--a[x2].health;
if (a[x2].health == 0) all_kill(x1, x2);
return ;
}
for (int j = 1, k = 1;;) {
while (a[x2].card[j] != 'K' && j <= a[x2].cardnum) ++j;
if (a[x2].cardnum < j) {
--a[x2].health;
if (a[x2].health == 0) all_kill(x1, x2);
return ;
} else
a[x2].card[j] = 'X';
while (a[x1].card[k] != 'K' && k <= a[x1].cardnum) ++k;
if (a[x1].cardnum < k) {
--a[x1].health;
if (a[x1].health == 0) all_kill(x2, x1);
return ;
} else
a[x1].card[k] = 'X';
}
}
void ManyShoots(int x) {
for (int i = a[x].next; i != x; i = a[i].next) {
if (!NoMoreHurts(x, i, true)) {
int j;
for (j = 1; j <= a[i].cardnum; ++j) {
if (a[i].card[j] == 'D') {
a[i].card[j] = 'X';
break;
}
}
if (a[i].cardnum < j) {
--a[i].health;
if (i == 1 && identity[x] == 'X')
identity[x] = 'L';
if (a[i].health == 0)
all_kill(x, i);
if (ended) return ;
}
}
}
}
void round() {
if (fpnum == 0) return ;
ended = false;
for (int i = 1; i; i = a[i].next) {
for (int j = 1; j <= 2; ++j)
GetCard(i);
bool can_kill = true;
for (int j = 1; j <= a[i].cardnum; ++j) {
if (a[i].card[j] != 'X') {
if (a[i].health == 0) break;
if (a[i].card[j] == 'P') {
if (a[i].health != 4) {
a[i].card[j] = 'X';
++a[i].health;
}
} else if (a[i].card[j] == 'K') {
if (can_kill || a[i].Z) {
if ((a[i].identity != 'M' || identity[a[i].next] == 'L' || identity[a[i].next] == 'F') && (a[i].identity != 'Z' || identity[a[i].next] == 'F') && (a[i].identity != 'F' || identity[a[i].next] == 'Z' || identity[a[i].next] == 'M')) {
a[i].card[j] = 'X';
kill(i, a[i].next);
identity[i] = a[i].identity;
can_kill = false;
if (ended) return ;
}
}
} else if (a[i].card[j] == 'F') {
if (a[i].identity == 'F') {
a[i].card[j] = 'X';
battle(i, 1);
identity[i] = a[i].identity;
if (ended) return ;
j = 0;
} else {
for (int k = a[i].next; k != i; k = a[k].next) {
if ((a[i].identity == 'M' && (identity[k] == 'L' || identity[k] == 'F')) || (a[i].identity == 'Z' && identity[k] == 'F')) {
a[i].card[j] = 'X';
battle(i, k);
identity[i] = a[i].identity;
if (ended) return ;
j = 0;
break;
}
}
}
} else if(a[i].card[j] == 'N') {
a[i].card[j] = 'X';
NanZhuInvade(i);
if (ended) return ;
j = 0;
} else if (a[i].card[j] == 'W') {
a[i].card[j] = 'X';
ManyShoots(i);
if (ended) return ;
j = 0;
} else if (a[i].card[j] == 'Z') {
a[i].card[j] = 'X';
a[i].Z = true;
j = 0;
}
}
}
}
}
int main() {
std :: scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
a[i].next = i + 1;
a[i].last = i - 1;
}
a[1].last = n;
a[n].next = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j < maxm; ++j)
a[i].card[j] = 'X';
char current_str[maxn];
std :: scanf("%s", current_str);
a[i].identity = current_str[0];
for (int j = 1; j <= 4; ++j) {
std :: scanf("%s", current_str);
a[i].card[j] = current_str[0];
}
if (a[i].identity == 'F') ++fpnum;
a[i].cardnum = a[i].health = 4;
a[i].Z = false;
}
identity[1] = 'M';
for (int i = 2; i <= n; ++i)
identity[i] = 'X';
for (int i = 1; i <= m; ++i) {
char current_card[maxn];
std :: scanf("%s", current_card);
cards[m - i + 1] = current_card[0];
}
round();
if (a[1].health > 0)
puts("MP");
else
puts("FP");
for (int i = 1; i <= n; ++i) {
if (a[i].health <= 0)
puts("DEAD");
else {
for (int j = 1; j <= a[i].cardnum; ++j)
if (a[i].card[j] != 'X') {
putc(a[i].card[j], stdout);
putc(' ', stdout);
}
putc('\n', stdout);
}
}
return 0;
}