UOJ 关羽下象棋 [分类讨论]

UOJ 关羽下象棋 [分类讨论]_第1张图片
UOJ 关羽下象棋 [分类讨论]_第2张图片
UOJ 关羽下象棋 [分类讨论]_第3张图片

思路:
第一个子任务里,车和卒是同一行的,显然车只要直接把卒吃掉即可。

根据算法一的思路,如果卒和车在同一行或者同一列,我们直接输出 1。好了下面我们讨论 x1≠x2, y1≠y2 的情况。

诶,我们接着来看第四个子任务!为什么直接跳到了第四个呢,因为我们需要想想一般情况下这个题的答案到底是多少。想要分类讨论,就要先想想答案最大是多少。而在第四个子任务里,卒和车都在棋盘靠中心的位置,所以我们至少可以不用考虑棋盘边界对答案的影响。

卒和车都是左右对称的,所以我们不妨设 y1y2,我们可以做一个水平的镜像对称。虽然不一定会体现在代码里,但至少思考的时候我们不用考虑车在卒左边的情况了。

现在我们随手画一个车在卒右边的情况:
UOJ 关羽下象棋 [分类讨论]_第4张图片
诶,然后我们来试试怎么把卒吃掉。我们先跑到卒的下面一行,这样卒就不敢往下走了,只能左右乱窜。然后我们就像样例一的第二组数据一样做:在卒的下面一行撒一行毒雾出来,卒仍然只能左右乱窜;现在我们跑到卒所在的那一行,卒仍然只能左右乱窜;接下来我们就把它吃掉。这样我们就得到了一个四步将卒吃掉的策略。

UOJ 关羽下象棋 [分类讨论]_第5张图片

那么,只要不在同一行同一列,就肯定是 4 了吗?如果卒和车离得很远的话,确实没有任何办法再优化 —— 首先如果卒既可以横向走,也可以向下走,那么卒肯定没办法在一步内被你吃掉,所以在吃掉卒之前,我们要么用毒封住卒的左右两侧,要么用毒封住下面。但封住左右两侧是需要走很多步的。因为如果你先封左边,卒可以往反方向走,导致你只能努力把它卡在某两列或者某三列,然后它就可以在里面继续乱走。经过一番尝试你会发现先封左侧或者先封右侧都是不可能比 4 步更优的。如果要封下面,那么晚封不如早封,所以就变成上图的那个策略了。

所以就是要考虑卒和车靠得很近的情况。可以发现,只要初始的时候不在卒所在的下一行,好像都没什么意义,还是得走到那行放毒。。如果在卒所在的下一行,那么上图的策略就可以省掉第一步了,于是 3 步就可以吃掉卒。(当然你看样例也会发现有这个情况)

特判掉这个情况,就可以过第四个子任务了。

通过上述思路理清楚答案不超过 4,或者通过看样例之类的其他方式攒足了对“答案很小”的信心之后,你就可以使用暴力美学解决本题了。我认为也是比赛时最经济的一种做法。

因为答案不超过 4,所以卒之多只会走 3 步,而车走到离卒太远的地方也毫无意义。

所以我们就可以在卒的初始位置附近,向各个方向延申常数格(仔细想想会发现常数取 4 就足够了,不够自信也可以取一点,再逐步减小看看是否影响答案),然后只在这个小范围内进行搜索。如果车初始时不在这个范围内,不妨把车直接挪到离车最近的格点。

好的,迭代加深!博弈搜索!游戏规则也不是很复杂,按照题面把代码写出来就好了!赛场上大部分 AC 的选手正是用的这一暴力美学。

要注意的是搜索时我们要标记哪些位置是车曾经走过的。如果用布尔数组来存储,回溯时不方便还原。所以你可以用 int 数组存储每个位置被车经过的次数,这样递归时加加,回溯时减减就好了。

至于时间复杂度,你可以把所有可能的数据都试一遍,就会发现跑得贼快。这样大胆交一发,就直接过了。如果你使用的是这种做法,本题难点其实是你如何在尽量短的时间内,写出一份正确的博弈搜索代码。我看到有些选手的实现方式十分简洁,大家可以找来看看 233

一种实现下,搜索树的一个上界是 1+39+392,因为每一步为车的决策数最大为 13,卒最大为 3,并且如果第三步不能直接吃掉则可以剪枝。

期望得分 100 分。

#include 
#include 
#include 
using namespace std;

const int N = 100;

int quick_solve(int x1, int y1, int x2, int y2) {
    int n, m;
    n = m = N;

    if (x1 == x2 || y1 == y2) {
        return 1;
    }

    if (y1 > y2) {
        y1 = m - y1 + 1;
        y2 = m - y2 + 1;
    }

    if (x1 == n) {
        return 2;
    }
    if (y1 == 1) {
        if (x2 == x1 + 1 || y2 == 2) {
            return 2;
        } else {
            return 3;
        }
    }
    if (y1 == 2) {
        if (x2 == x1 + 1 || y2 == 3) {
            return 3;
        }
        if (x2 <= x1 && y2 == 4) {
            return 3;
        }
    }
    if (x2 == x1 + 1) {
        return 3;
    }
    if (x1 == n - 1) {
        return 3;
    }
    return 4;
}

int main()
{
    int nT;
    cin >> nT;

    while (nT--) {
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        cout << quick_solve(x1, y1, x2, y2) << endl;
    }

    return 0;
}

你可能感兴趣的:(分类讨论)