洛谷 P6970 [NEERC2016]Game on Graph 题解

一、题目:

洛谷原题

codeforces原题

二、思路:

刚看完这道题的思路,还感觉挺清晰的。但是一到打代码的时候,就迷得不行🎃。尤其是拓扑排序那一块,费了我很长时间才看懂。

首先来说一下这道题的大致思路。为了简化我们的书写,将 Gennady 记为 A,将 Georgiy 记为 B。设状态 \((x, A/B)\) 表示当前在点 \(x\),下一步该 \(A/B\) 走了。我们摒弃官方题解的思路,因为官方题解的思路会让人陷入混乱。

我们首先来考虑平局的情况,目标是确定出哪些点一定平局,哪些点可以分出胜负。

  1. \((x, A)\) 能分出胜负当且仅当对于 \(x\) 任意的后继 \(y\)\((y,B)\) 都能分出胜负。
  2. \((x,B)\) 能分出胜负当且仅当存在 \(x\) 的后继 \(y\)\((y,A)\) 能分出胜负。

所有出度为 0 的点一定能分出胜负,然后类似拓扑排序迭代即可。但是请注意,这里的迭代过程与拓扑排序还是有很大区别的。具体的迭代方法请参见代码注释。

那么不能分出胜负的点我们就不用去管了。接下来考虑对于这些能分出胜负的点以及它们所构成的残图,我们该怎样确定出每个点的胜负情况。这个问题就是经典的博弈论问题了。我们可以再进行一遍拓扑排序求解。

有些细心的同学可能会发现,第二遍拓扑排序完之后,还会有一些点没有被确定出胜负状态。但是我们可以断定,这些点一定是 A 赢。为什么呢?考虑这些点一定是:A 既不能在这些点将游戏变成平局,B 又不能在这些点赢得比赛。A 又不想输,B 又不想平局,结果只能有一个,就是让 A 赢。

三、代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>

using namespace std;
#define FILEIN(s) freopen(s, "r", stdin);
#define FILEOUT(s) freopen(s, "w", stdout)
#define mem(s, v) memset(s, v, sizeof s)

inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int maxn = 100005;

int n, m;
int deg[maxn];
bool is_draw[maxn][2];
int win[maxn][2], d[maxn][2];

vector<int>rev_linker[maxn], linker[maxn];

int main() {
    n = read(); m = read();
    for (int i = 1; i <= m; ++ i) {
        int x = read(), y = read();
        rev_linker[y].push_back(x); // 反图
        linker[x].push_back(y);
        ++ deg[x];
    }
    queue<pair<int, int> >q;
    for (int i = 1; i <= n; ++ i) {
        if (!deg[i]) q.push({i, 0}), q.push({i, 1});
        else is_draw[i][0] = is_draw[i][1] = 1;
    }
    while(q.size()) {
        int x = q.front().first, p = q.front().second; q.pop();
        for (int cur : rev_linker[x]) {
            if ((p == 0 && is_draw[cur][1]) || (p == 1 && !(-- deg[cur])))
                is_draw[cur][p ^ 1] = 0, q.push({cur, p ^ 1}); // 注意这里将点放入队列中的条件和经典的拓扑排序大不相同。对于B来说,只要它能分出胜负,就要把这个状态放入队列来更新以后的状态;而对于A来讲,只有当它的所有后继都能分出胜负,它才确定下来能分出胜负,才需要将它放入队列。
        }
    }
    for (int i = 1; i <= n; ++ i) {
        if (!linker[i].size()) q.push({i, 0}), q.push({i, 1});
        else win[i][0] = win[i][1] = -1;
        for (int v : linker[i]) d[i][0] += !is_draw[v][1], d[i][1] += !is_draw[v][0];
    }
    while (q.size()) {
        int x = q.front().first, p = q.front().second; q.pop();
        for (int v : rev_linker[x])
            if (!is_draw[v][p ^ 1]) {
                if (win[x][p]) { // 更新的方法类似上面的拓扑排序。
                    if (!(-- d[v][p ^ 1]))
                        q.push({v, p ^ 1}), win[v][p ^ 1] = 0;
                }
                else {
                    if (win[v][p ^ 1] == -1)
                        q.push({v, p ^ 1}), win[v][p ^ 1] = 1;
                }
            }
    }
    for (int i = 1; i <= n; ++ i) { // 还没确定胜负状态的点一定是A赢。
        if (win[i][0] == -1) win[i][0] = 1;
        if (win[i][1] == -1) win[i][1] = 0;
    }
    for (int i = 1; i <= n; ++ i) putchar(is_draw[i][0] ? 'D' : (win[i][0] ? 'W' : 'L'));
    puts("");
    for (int i = 1; i <= n; ++ i) putchar(is_draw[i][1] ? 'D' : (win[i][1] ? 'W' : 'L'));
    puts("");
    return 0;
}
posted @ 2021-06-27 12:13  蓝田日暖玉生烟  阅读(86)  评论(0编辑  收藏  举报