题解 P1199 【三国游戏】

· · 题解

这是一道典型的博弈论题目,所以我先提一下博弈论

1.博弈论入门

你和你的朋友,两个人一起玩游戏:桌子上有一堆石头,每次你们轮流拿掉 1 - 3 块石头。 拿掉最后一块石头的人就是获胜者。你作为先手。

你们是聪明人,每一步都是最优解。 编写一个程序,来判断你是否可以在给定石头数量的情况下赢得游戏。

示例:

输入: 4 输出: false 解释: 如果堆中有 4 块石头,那么你永远不会赢得比赛; 因为无论你拿走 1 块、2 块 还是 3 块石头,最后一块石头总是会被你的朋友拿走。

这个题目的典型特征:只有两个玩家,游戏规则固定,游戏里面有可变的数据(这题是石头的数量),询问你是否必胜? 看到这种描述你就要明白,你遇到一个博弈论的题目啦,千万注意绝对不是让你用程序去模拟题目的游戏过程再得出结果的。你要在脑中分析出过程,重点分析两点:1.什么时候可以判定胜利者,2.先手的影响。

题目描述了石头数量为 4 的情况,数量为 4 的时候先手的那个人必输,因为他一次拿不完,但是又不得不拿,不管他拿多少后手的人只要把剩下的都拿完就赢了。这个时候发散思维就派上用场了,这个示例给我们的启示是:只要场上剩下的石头数量是 4 的倍数,那么后手必胜,假设这一轮先手的一方拿了a个,那么后手的一方就拿 4 - a 个,按照这样的玩法,每轮消耗4块石头。玩完最后一轮后手就赢了。 所以这个游戏后手必胜规则是:当石头数量是 4 的倍数。

因为后手输了就代表先手赢了,所以先手必胜的条件是反着的:当石头数量不是 4 的倍数。

到这里这个题目已经解出来了,如果你好奇石头数量不能被 4 整除时先手怎么赢的,那么可以看这里: 对于这个游戏来说,假设原本的游戏规则是共有 n 个石头,A 先手,B后手,如果 A 总是先手拿 m 个石头,那么这个游戏等同于共有 n - m 个石头,B 先手, A 后手的一场游戏。所以,当石头数量不能被 4 整除的时候,先手拿走被4整除的余数数量的石头就能保证必胜。

拿石头这个游戏是经典的“巴什博弈”。

---------------分割线----------------
首先我们要明确这是一个博弈论的题目,不要去模拟过程,想办法分析获胜的方式。计算机和玩家对抗的方式比较傻,第一感觉是玩家肯定能赢,这个题有10 组用例,如果玩家输了只需要输出0即可,如果这个游戏真的有输有赢,那直接cout<<0; 的同学肯定能得一部分分数,所以我直接输出 0 然后提交了代码(某oj,主要是怕影响AC率),结果一分未得,说明在博弈论的视角下,这个游戏的玩家是必胜的。所以最终输出的格式肯定是

1 X

X 如何得到呢,读懂题意不难发现 X 是一组武将的默契值,这个数不是计算出来的,而是输入数据中的某个数字,而且按照题意这个数字越大越好,对比了一下两个用例,第一组用例输出的是 32, 第二组输出的是 77. 刚好都是所有数据中第二大的数字,由于计算机的阻挠,玩家不可能选到默契值最大的武将组合,那么玩家最终获胜的数字是否是次大的那个数字呢?为了验证我的猜测,我写了个很简单的程序,找到所有输入数据中第二大的数字,结果只得了 20 分,也就是说真实的情况没这么简单,刚好只有这两组示例用例满足我的猜测而已。

看来这题没法继续骗分了QWQ,我必须得认认真真分析了。对比巴什博弈的题目,我要从题目给的唯一的一组选将过程中寻找答案。巴什博弈中不管石头总数有多少,每一轮玩家都要想办法拿 4 - a 块石头。那么这个游戏中小涵是如何选择的呢

我把小涵和电脑玩的那一局复盘了一下,由于计算机是被动的跟着小涵选的,所以我们只要分析出小涵3次选将的依据,这个题就做出来了。横着看上图,小涵第一次拿了 5,第 5 行确实大数字很多,计算机为了阻挠小涵,所以拿了 4,这样小涵就拿不到默契值为 33 的组合了。这个时候小涵选择了 3,计算机选了 1,对比这两步可以发现,小涵前两轮拿到了 32 这个默契值,而这个默契值是全场第二高的,这是我刚才分析过的,结果已经被证明是片面的想法了。

正确的思路是:小涵和电脑都拿不到每个武将的最大默契值,因为小涵每拿起最大组合的其中一个,另一个就被电脑拿走了,但是电脑也无法占有该组合的最大值,因为另一个在小涵手里,这个组合的最大值其实就作废了。但是小涵在第二次选将时可以拿到第一次选中的武将的次大默契值组合。在双方都拿不到最大默契值的情况下,显然谁拿到次大默契组合的最大值,谁就赢了。

以示例中的选将为例,武将1-6的次大默契值罗列出来:


小涵拿到 5 和 3 之后,小涵就已经赢了,电脑拿了4之后无法组合出比 32更大的值。所以按照这个逻辑,小涵第一轮和第二轮拿到 5 和 3,剩下的几步就可以随便选了。
下面就上完整AC代码

关爱生命,拒绝抄袭

#include<bits/stdc++.h>
using namespace std;

//放到 main 外面去定义可以得到的好处:数组 mo 的中所有元素都会被默认赋值为 0
int mo[501][501],n;
int main()
{
    cin>>n;
    // 构造二维数组要用嵌套的 fo r循环,i 写入到数组第一维,j 写到数组第二维
    for(int i=1;i<=n;i++) {
        for(int j=i+1;j<=n;j++) {
            cin>>mo[i][j];
            //(i,j) 的对称位置是(j,i)
            mo[j][i]=mo[i][j];
        }
    }
    //ans 保存二维数组每一行次大值中的最大值
    int ans = 0;
    //对数组的第二维进行排序
    for(int i=1;i<=n;i++)
    {
        //对 mo 的每一行都排序,sort 默认是从小到大的
        sort(mo[i],mo[i]+n+1);
        //排序后每一行第二大的值是 mo[i][n-1]
        if(mo[i][n-1] > ans) 
            ans = mo[i][n-1];
    }

    cout<<1<<endl<<ans;
    return 0;
}