洛谷Oj-P1288 取數遊戲II-博弈

問題描述:
有一個取數的遊戲。初始時,給出一個環,環上的每條邊上都有一個非負整數。這些整數中至少有一個0。然後,將一枚硬幣放在環上的一個節點上。兩個玩家就是以這個放硬幣的節點爲起點開始這個遊戲,兩人輪流取數,取數的規則如下:
(1)選擇硬幣左邊或者右邊的一條邊,並且邊上的數非0;
(2)將這條邊上的數減至任意一個非負整數(至少要有所減小);
(3)將硬幣移至邊的另一端。
如果輪到一個玩家走,這時硬幣左右兩邊的邊上的數值都是0,那麼這個玩家就輸了。
如下圖,描述的是Alice和Bob兩人的對弈過程,其中黑色節點表示硬幣所在節點。結果圖(d)中,輪到Bob走時,硬幣兩邊的邊上都是0,所以Alcie獲勝。
這裏寫圖片描述
(a)Alice (b)Bob (c)Alice (d)Bob
現在,你的任務就是根據給出的環、邊上的數值以及起點(硬幣所在位置),判斷先走方是否有必勝的策略。
80分代碼(無腦博弈搜索):

int n;
int val[30];//邊上的數值
inline int loop(int id)//返回合法的下標值,inline加快速度
{
    if(id == n + 1)
        id = 1;
    if(id == 0)
        id = n;
    return id;
}
//區分硬幣的位置和邊的位置,以及二者的相互轉化!圖解:1①2②……nⓃ,純數字爲邊,帶圈數字爲硬幣
bool dfs(int pos)//硬幣的位置,取值範圍爲[1,n]
{
    //pos爲硬幣的左邊,pos + 1爲硬幣的右邊
    //如果硬幣的左右兩邊都沒得選,就輸啦
    if(val[loop(pos)] == 0 && val[loop(pos + 1)] == 0)
        return false;
    //如果硬幣的左邊不爲0,就選擇左邊
    if(val[loop(pos)] != 0)
    {
        for(int i = 1; i <= val[loop(pos)]; ++i)
        {
            val[loop(pos)] -= i;//取走i個
            if(dfs(loop(pos - 1)) == true)//如果對手必勝
            {
                val[loop(pos)] += i;//別忘了恢復
                return false;//則我方必敗
            }
            val[loop(pos)] += i;//恢復
        }
    }
    //如果硬幣的右邊不爲0,就選擇右邊
    if(val[loop(pos + 1)] != 0)
    {
        for(int i = 1; i <= val[loop(pos + 1)]; ++i)
        {
            val[loop(pos + 1)] -= i;//取走i個
            if(dfs(loop(pos + 1)) == true)//如果對手必勝
            {
                val[loop(pos)] += i;//別放了恢復
                return false;//則我方必敗
            }
            val[loop(pos)] += i;//恢復
        }
    }
    return true;//對方不能必勝,則我方必勝
}
int main()
{
    scanf("%d",&n);//輸入環上的點數
    for(int i = 1; i <= n; ++i)
        scanf("%d",&val[i]);//輸入邊的數值
    if(dfs(n) == true)
        puts("YES");
    else
        puts("NO");
    return 0;
}

AC代碼:

int a[50];
int main()
{
    int n;
    cin >> n;
    for(int i = 1; i <= n; ++i)
        cin >> a[i];
    for(int i = 1; i <= n; ++i)
    {
        if(a[i] == 0)
        {
            if(i % 2 == 0)
            {
                puts("YES");
                return 0;
            }
            else
                break;//如果找到了爲0的邊就break!我們要找的是第一個0
        }
    }
    for(int i = n; i >= 1; --i)
    {
        if(a[i] == 0)
        {
            if((n - i + 1) % 2 == 0)
            {
                puts("YES");
                return 0;
            }
            else
                break;//如果找到了爲0的邊就break!我們要找的是第一個0
        }
    }
    puts("NO");
    return 0;
}

解決方法:
哎,知識水平還是太低,總是想用暴搜來解決問題,結果只有80分,經過並沒有什麼用的優化:

    int p1 = loop(pos - 1);
    int p2 = loop(pos);
    int p3 = loop(pos + 1);

也還是80分,看來做博弈題不能上來就無腦暴力,還是要動動腦子的。博弈搜索也不是完全不行,需要有進一步的優化。
這個題目的關鍵數字是0,環中至少有一個0。
必敗態是0,硬幣,0,如何可以把對方逼入這個狀態呢
其中一個0是初始時便存在的0,另外的一個0便是我們生成的
可以像趕鴨子上架一樣,沿着一個方向,取光所有的數,這樣無論對方取多少,是否取光,都無法逆向移動

可以發現,先手是否有必勝策略與初始時硬幣與權值爲0的邊的“距離(有點難以描述)”有很大的關係
距離爲偶數,先手必勝,否則沒有必勝策略
由於是環,一開始可以向兩邊移動,所以在兩個方向上都要判斷一下
說是任取,其實對方纔不會這麼幹,這麼幹等於自殺。所以如果假設雙方足夠聰明,則每次取完後,邊上的數字都會變爲0。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章