1. 程式人生 > 實用技巧 >[luogu p2482] [SDOI2010]豬國殺

[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\) 次「殺」來攻擊;
    2. 任何牌被使用後被棄置(武器是裝備上);被棄置的牌以後都不能再用,即與遊戲無關。

各種牌介紹

每張手牌用 \(1\) 個字母表示,字母代表牌的種類。

基本牌

  • 『桃 / \(\texttt{P}\)』在自己的回合內,如果自己的體力值不等於體力上限,那麼使用 \(1\) 個桃可以為自己補充 \(1\)
    點體力,否則不能使用桃;桃只能對自己使用;在自己的回合外,如果自己的血變為 \(0\) 或者更低,那麼也可以使用。
  • 『殺 / \(\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;
}

評測結果

評測結果