【题解】[COCI2020-2021#5] Magenta

solution:

图论 + 博弈论。

我们称操作前两个棋子间距离为偶数的一方为 猎人 ,不难发现 猎人 只会 win 或者 draw。这是因为终止状态中棋子的距离为 0 0 0 ,而 猎人 操作后一定是奇数,所以不可能走到对方的棋子上。当然要特判 猎人 一条边都走不到的情况。

考虑将图中所有的红色边都删除,形成若干连通块。将猎人连通块外的节点设为 1 1 1 。我们从猎人开始搜索,并记录下到达当前节点的最小步数。然后从猎物开始搜索,当且仅当当前步数小于所标记步数或当前点权值为 1 1 1 时这个点可以扩展,如果遍历到一个权值为 1 1 1 的出口并且这个点有后续连接的权值为 1 1 1 的点,则形成平局;否则无法逃脱。

一波剧烈恶心讨论。

  1. a a a 先手没有地方可走,输出 Marin ;
  2. b b b 没有合法出边,输出 Paula;
  3. L o s e Lose Lose 能走到权值为 1 1 1 的点,并且这个点有后续连接的权值为 1 1 1 的点(这类节点可以预处理),输出 Magenta ;
  4. 否则输出 Win==a?Marin:Paula。

很考验耐性。。

时间复杂度 O ( n ) O(n) O(n)

#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define PII pair<int,int>
using namespace std;
const int mx = 1e5 + 5;
int n, a, b, Win, Lose, vis[mx], vis2[mx], fa[mx], dis[mx], l, cnt, cnt2, res;
char op[10];
vector<PII> g[mx];
void dfs(int u, int f) {
    fa[u] = f;

    for (auto v : g[u]) {
        if (v.first == f)
            continue;

        dfs(v.first, u);
    }
}
void dfs2(int u, int fa, int step) {
    dis[u] = step, cnt++;
    vis[u] = 1;

    for (auto v : g[u]) {
        if (v.first == fa || v.second == Win)
            continue;

        dfs2(v.first, u, step + 1);
    }
}
void dfs3(int u, int fa, int step) {
    if (vis2[u]) {
        printf("Magenta");
        exit(0);
    }

    for (auto v : g[u]) {
        if (v.first == fa || v.second == Lose)
            continue;

        if (step + 1 - (Lose == a) < dis[v.first]) {
            dfs3(v.first, u, step + 1);
        }
    }
}
void dfs4(int u, int fa) {
    cnt++;

    for (auto v : g[u]) {
        if (v.first == fa || v.first == b || v.second == a)
            continue;

        dfs4(v.first, u);
    }
}
void dfs5(int u, int fa) {
    cnt2++;

    for (auto v : g[u]) {
        if (v.first == fa || v.second == b)
            continue;

        dfs5(v.first, u);
    }
}
signed main() {
    //  freopen("data.in","r",stdin);
    scanf("%d%d%d", &n, &a, &b);

    for (int i = 1; i < n; i++) {
        int u, v, w;
        scanf("%d%d%s", &u, &v, op);

        if (op[0] == 'c')
            w = a;
        else if (op[0] == 'p')
            w = b;
        else
            w = 0;

        g[u].push_back(make_pair(v, w));
        g[v].push_back(make_pair(u, w));
    }

    dfs(1, 0);

    for (int i = a; i; i = fa[i]) {
        vis[i] = 1;
        dis[fa[i]] = dis[i] + 1;
    }

    if (vis[b]) {
        l = dis[b];
    } else {
        for (int i = b; i; i = fa[i]) {
            if (vis[fa[i]]) {
                l = dis[fa[i]] + dis[i] + 1;
                break;
            }

            dis[fa[i]] = dis[i] + 1;
        }
    }

    Win = (l & 1) ? b : a, Lose = (l & 1) ? a : b;

    for (int i = 1; i <= n; i++)
        dis[i] = INF, vis[i] = 0;

    dfs4(a, 0);

    if (cnt == 1) {
        printf("Marin");
        return 0;
    }

    dfs5(b, 0);

    if (cnt2 == 1) {
        printf("Paula");
        return 0;
    }

    //  printf("%d %d\n",Win,Lose);
    dfs2(Win, 0, 0);

    for (int i = 1; i <= n; i++) {
        if (vis[i])
            continue;

        for (auto u : g[i]) {
            if (!vis[u.first] && u.second != Lose) {
                vis2[i] = 1;
                break;
            }
        }
    }

    dfs3(Lose, 0, 0);
    printf("%s", (Win == a) ? "Paula" : "Marin");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值