GAME on cactus | [POI 2016] Hydrorozgrywka 题解

前言

题目链接:洛谷GAME on cactus

为啥没桥啊,没用到什么性质啊。

为啥没重边啊,没用到什么性质啊。

推荐阅读:《圆方树学习笔记 —— 一种关于点双连通分量的思考方式》

题意简述

原题无桥,不妨加强。原题无重边,不妨加强。

\(n\) 个点、\(m\) 条边的仙人掌上的博弈游戏。玩家可以选择一条没被经过的边,将棋子从边的一边移动到另一边。无法移动的玩家输。对于每一个点,求出棋子一开始在该点,先手是否有必胜策略。

\(n\leq5\times10^5\)

题目分析

显然建出圆方树,树形 DP。设 \(f_u=0/1\) 表示先手从 \(u\) 开始,只考虑 \(u\) 的子仙人掌,是否必胜 / 必败。

但是这个状态有缺陷,因为有可能绕了一圈回到 \(u\),继续往子仙人掌以外走。总共有以下四种情况:

  1. 先手如果往子仙人掌里走必败,即被堵在了 \(u\) 的某一个子孙中;
  2. 先手如果往子仙人掌里走必胜,后手被堵在了 \(u\) 的某一个子孙中;
  3. 先手如果往子仙人掌里走,回到 \(u\),轮到后手,且不会再往子仙人掌里走。
  4. 先手如果往子仙人掌里走,回到 \(u\),轮到先手,且不会再往子仙人掌里走。

可以直接记 \(f_u=0/1/2/3\) 吗?2,3 局面并不是互斥的,也就是说,先手可能可以选择先手回到 \(u\),也可以选择后手回到 \(u\)

所以记 \(f_{u,0/1/2/3}\in\{\text{true},\text{false}\}\) 表示先手走进 \(u\) 的子仙人掌中,先手是否必胜、先手是否必败、先手是否可以选择先手 / 后手回到 \(u\) 走下一步。只有在 \(f_{u,0/1}=\text{false}\) 的前提下,\(f_{u,2/3}\) 才是有意义的。当然,也有可能 \(f_{u,0/1/2/3}=\text{false}\),这种情况下由后手来决定谁回到 \(u\)

对于根节点,只要 \(f_{u,0/3}=\text{true}\) 就是必胜的。转移的话,分情况讨论。

  1. \(u\) 是方点

    1. 如果这个点双不是个环(即两点被桥连接)

      如果 \(f_{v,1}=\text{true}\)\(f_{v,0}=\text{false}\land f_{v,3}=\text{false}\) 说明后手必败,先手必胜,\(f_{u,0}=\text{true}\),其他均为 \(\text{false}\)

      否则如果 \(f_{v,0}=\text{true}\)\(f_{v,3}=\text{true}\) 说明后手必胜,先手必败,\(f_{u,1}=\text{true}\),其他均为 \(\text{false}\)

      不存在其他情况。

    2. 如果这个点双是个环

      一定会按环序依次经过环上的点,不妨先考虑顺时针。显然,一旦经过一个子仙人掌,这个子仙人掌之后就不会被访问到了。

      先考虑是否存在必胜必败的情况。

      1. 经过一个点 \(f_{v,1}=\text{true}\)

        先手显然会跳过这个点,直接走到下一个点。

      2. 经过一个点 \(f_{v,0}=\text{true}\)

        先手显然选择走进这个子仙人掌,必胜

      3. 经过一个点 \(f_{v,2}=\text{true}\)

        先手走不走无所谓,都会走到下一个点。

      4. 经过一个点 \(f_{v,3}=\text{true}\)

        先手既可以选择走进去,换成后手,走向下一个点,也可以直接走到下一个点。也就是说,先手可以选择是否改变先后手。那么后继状态如果必胜,先手不会改变先后手,否则先手改变先后手,同样必胜

      5. 经过一个点 \(f_{v,2}=f_{v,3}=\text{false}\)

        先手如果走进去,后手就能够根据下一步的必胜必败,使得先手必败。所以先手不会走进去,而是直接前往下一个点。

      不存在其他情况。

      那么只要有一个 \(f_{v,0/3}=\text{true}\),找到第一次出现的位置,看看奇偶性,就能判断先手必胜还是必败。同样逆时针做一遍。如果顺逆时针有一种情况先手必胜,那么先手就必胜,\(f_{u,0}=\text{true}\),否则无论怎样,先手必败,\(f_{u,1}=\text{true}\)

      如果没有 \(f_{v,0/3}=\text{true}\),考虑求 \(f_{u,2/3}\)

      发现没有必胜情况,先手都是往下一步走,所以如果是奇环,必定是先手回到 \(u\)\(f_{u,3}=\text{true}\);否则一定是后手回到 \(u\)\(f_{u,2}=\text{true}\)

  2. \(u\) 是圆点

    如果有个方儿子 \(f_{v,0}=\text{true}\),那么 \(u\) 必胜,\(f_{u,0}=\text{true}\)

    如果每个方儿子都是 \(f_{v,1}=\text{true}\)\(f_{v,2}=f_{v,3}=\text{false}\),那么 \(u\) 必败,因为 \(u\) 必须要行动,往任意一个孩子走都是必败的,\(f_{u,1}=\text{true}\)

    如果满足 \(f_{v,3}=\text{true}\) 的方儿子个数为奇数,那么 \(u\) 就能改变先后手,即:先走到一个孩子里改变一次先后手,之后每次后手尝试改变先后手之后,\(u\) 都能通过走到另一个孩子里改回来,\(f_{u,3}=\text{true}\)

    如果满足 \(f_{v,3}=\text{true}\) 的方儿子个数为偶数,并且有一个 \(f_{v,2}=\text{true}\)\(u\) 就能保持先手,之后每次后手尝试改变先后手之后,\(u\) 都能通过走到另一个孩子里改回来,\(f_{u,2}=\text{true}\)

我们惊奇地发现,不存在 \(f_{u,2/3}\) 同时为 \(\text{true}\) 的情况。但是存在 \(f_{u,0/1/2/3}\) 全为 \(\text{false}\) 的情况。其实也没什么用。

于是我们有了 \(\mathcal{O}(n(n+m))\) 的做法。套路化用换根 DP 做到 \(\mathcal{O}(n+m)\),考验大家码力的时候到了。

代码

Gen 见 link

$\mathcal{O}(n(n+m))$
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int N = 5e5 + 10;
const int M = N << 1;
const int N2 = N << 1;

template <size_t _N, size_t _M>
class Graph {
public:
    Graph(): tot(1) {}
    struct node {
        int v, nxt;
    } edge[_M];
    int head[_N], tot;
    void add(int u, int v) {
        edge[++tot] = { v, head[u] };
        head[u] = tot;
    }
};
Graph<N, M << 1> G;
Graph<N2, N2> T;

int n, m;

int dcc_cnt;
int dis[N2], tfa[N2];

namespace $build {
    
int dfn[N], low[N], timer;
int stack[N], top;
void tarjan(int u, int fr) {
    dfn[u] = low[u] = ++timer;
    stack[++top] = u;
    for (int i = G.head[u]; i; i = G.edge[i].nxt) {
        if (i == (fr ^ 1)) continue;
        int v = G.edge[i].v;
        if (!dfn[v]) {
            tarjan(v, i);
            low[u] = min(low[u], low[v]);
            if (low[v] > dfn[u]) {
                ++dcc_cnt;
                T.add(u, dcc_cnt + n);
                T.add(dcc_cnt + n, -u);
                top--;
                T.add(dcc_cnt + n, -v);
                T.add(v, dcc_cnt + n);  // -x means bridge
            } else if (low[v] >= dfn[u]) {
                ++dcc_cnt;
                T.add(u, dcc_cnt + n);
                T.add(dcc_cnt + n, u);
                tfa[dcc_cnt + n] = u;
                while (true) {
                    int x = stack[top--];
                    dis[x] = ++dis[dcc_cnt + n];
                    T.add(dcc_cnt + n, x);
                    T.add(x, dcc_cnt + n);
                    tfa[x] = dcc_cnt + n;
                    if (x == v) break;
                }
            }
        } else {
            low[u] = min(low[u], dfn[v]);
        }
    }
}

}

bool f[N2][4];

void dfs(int u, int fa) {
    f[u][0] = f[u][1] = false;
    f[u][2] = f[u][3] = false;
    if (u <= n) {
        bool all = true;
        int cnt = 0;
        bool hav = false;
        for (int i = T.head[u]; i; i = T.edge[i].nxt) {
            int v = T.edge[i].v;
            if (v == fa) continue;
            dfs(v, u);
            cnt += f[v][3];
            hav |= f[v][2];
            if (f[v][0]) {
                f[u][0] = true;
            }
            all &= f[v][1] || (!f[v][2] && !f[v][3]);
        }
        if (!f[u][0]) {
            if (all) {
                f[u][1] = true;
            } else {
                if (cnt % 2 == 1) {
                    f[u][3] = true;
                } else {
                    if (hav) {
                        f[u][2] = true;
                    }
                }
            }
        }
    } else {
        vector<pair<int, bool>> vc;
        bool hav = false;
        int cnt = 1;
        for (int i = T.head[u]; i; i = T.edge[i].nxt) {
            int v = T.edge[i].v;
            if (v == fa || -v == fa) continue;
            ++cnt;
            if (v < 0) {
                v = -v;
                dfs(v, u);
                if (f[v][1]) {
                    f[u][0] = true;
                } else if (f[v][0]) {
                    f[u][1] = true;
                } else if (!f[v][3]) {
                    f[u][0] = true;
                } else {
                    f[u][1] = true;
                }
                return;
            } else {
                dfs(v, u);
                int d = v == tfa[u] ? 0 : dis[v];
                int o = fa == tfa[u] ? 0 : dis[fa];
                vc.emplace_back(d - o < 0 ? d - o + dis[u] + 1 : d - o, f[v][0] || f[v][3]);
                hav |= f[v][0] || f[v][3];
            }
        }
        if (hav) {
            sort(vc.begin(), vc.end());
            bool win = false;
            for (int i = 0; i < cnt - 1; ++i) {
                if (vc[i].second) {
                    win |= i % 2 == 1;
                    break;
                }
            }
            for (int i = cnt - 2; ~i; --i) {
                if (vc[i].second) {
                    win |= (cnt - 2 - i) % 2 == 1;
                    break;
                }
            }
            if (win) {
                f[u][0] = true;
            } else {
                f[u][1] = true;
            }
        } else {
            if (cnt % 2 == 1) {
                f[u][3] = true;
            } else {
                f[u][2] = true;
            }
        }
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1, u, v; i <= m; ++i) {
        scanf("%d%d", &u, &v);
        G.add(u, v), G.add(v, u);
    }
    $build::tarjan(1, 0);
    for (int i = 1; i <= n; ++i) {
        dfs(i, 0);
        puts(f[i][0] || f[i][3] ? "1" : "2");
    }
    return 0;
}
$\mathcal{O}(n+m)$
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int N = 5e5 + 10;
const int M = N << 1;
const int N2 = N << 1;

template <size_t _N, size_t _M>
class Graph {
public:
    Graph(): tot(1) {}
    struct node {
        int v, nxt;
    } edge[_M];
    int head[_N], tot;
    void add(int u, int v) {
        edge[++tot] = { v, head[u] };
        head[u] = tot;
    }
};
Graph<N, M << 1> G;
Graph<N2, N2> T;

int n, m;

int dcc_cnt;

namespace $build {
    
int dfn[N], low[N], timer;
int stack[N], top;
void tarjan(int u, int fr) {
    dfn[u] = low[u] = ++timer;
    stack[++top] = u;
    for (int i = G.head[u]; i; i = G.edge[i].nxt) {
        if (i == (fr ^ 1)) continue;
        int v = G.edge[i].v;
        if (!dfn[v]) {
            tarjan(v, i);
            low[u] = min(low[u], low[v]);
            if (low[v] > dfn[u]) {
                ++dcc_cnt;
                T.add(u, -(dcc_cnt + n));
                T.add(dcc_cnt + n, -v);
                top--;
            } else if (low[v] >= dfn[u]) {
                ++dcc_cnt;
                T.add(u, dcc_cnt + n);
                while (true) {
                    int x = stack[top--];
                    T.add(dcc_cnt + n, x);
                    if (x == v) break;
                }
            }
        } else {
            low[u] = min(low[u], dfn[v]);
        }
    }
}

}

bool f[N2][4], g[N2][4];

int all1[N2], all2[N2];
int cnt3[N2], cnt2[N2], cnt0[N2];
vector<bool> vc[N2];
int hav[N2];

void dfs(int u) {
    f[u][0] = f[u][1] = false;
    f[u][2] = f[u][3] = false;
    if (u <= n) {
        for (int i = T.head[u]; i; i = T.edge[i].nxt) {
            int v = T.edge[i].v;
            if (v < 0) v = -v;
            dfs(v);
            cnt3[u] += f[v][3];
            cnt2[u] += f[v][2];
            cnt0[u] += f[v][0];
            ++all1[u];
            all2[u] += f[v][1] || (!f[v][2] && !f[v][3]);
        }
        if (cnt0[u]) {
            f[u][0] = true;
        } else {
            if (all1[u] == all2[u]) {
                f[u][1] = true;
            } else {
                if (cnt3[u] % 2 == 1) {
                    f[u][3] = true;
                } else {
                    if (cnt2[u]) {
                        f[u][2] = true;
                    }
                }
            }
        }
    } else {
        for (int i = T.head[u]; i; i = T.edge[i].nxt) {
            int v = T.edge[i].v;
            if (v < 0) {
                v = -v;
                dfs(v);
                if (f[v][1]) {
                    f[u][0] = true;
                } else if (f[v][0]) {
                    f[u][1] = true;
                } else if (!f[v][3]) {
                    f[u][0] = true;
                } else {
                    f[u][1] = true;
                }
                return;
            } else {
                dfs(v);
                vc[u].emplace_back(f[v][0] || f[v][3]);
                hav[u] += f[v][0] || f[v][3];
            }
        }
        int cnt = vc[u].size() + 1;
        if (hav[u]) {
            bool win = false;
            for (int i = 0; i < cnt - 1; ++i) {
                if (vc[u][i]) {
                    win |= i % 2 == 1;
                    break;
                }
            }
            for (int i = cnt - 2; ~i; --i) {
                if (vc[u][i]) {
                    win |= (cnt - 2 - i) % 2 == 1;
                    break;
                }
            }
            if (win) {
                f[u][0] = true;
            } else {
                f[u][1] = true;
            }
        } else {
            if (cnt % 2 == 1) {
                f[u][3] = true;
            } else {
                f[u][2] = true;
            }
        }
    }
}

void redfs(int u) {
    if (u <= n) {
        for (int i = T.head[u]; i; i = T.edge[i].nxt) {
            int v = T.edge[i].v;
            bool tf[4] = { false, false, false, false };
            if (v < 0) {
                v = -v;
                int tcnt3 = cnt3[u] - f[v][3];
                int tcnt2 = cnt2[u] - f[v][2];
                int tcnt0 = cnt0[u] - f[v][0];
                int tall1 = all1[u] - 1;
                int tall2 = all2[u] - (f[v][1] || (!f[v][2] && !f[v][3]));
                if (tcnt0) {
                    tf[0] = true;
                } else {
                    if (tall1 == tall2) {
                        tf[1] = true;
                    } else {
                        if (tcnt3 % 2 == 1) {
                            tf[3] = true;
                        } else {
                            if (tcnt2) {
                                tf[2] = true;
                            }
                        }
                    }
                }
                if (tf[1]) {
                    g[v][0] = true;
                } else if (tf[0]) {
                    g[v][1] = true;
                } else if (!tf[3]) {
                    g[v][0] = true;
                } else {
                    g[v][1] = true;
                }
            } else {
                int tcnt3 = cnt3[u] - f[v][3];
                int tcnt2 = cnt2[u] - f[v][2];
                int tcnt0 = cnt0[u] - f[v][0];
                int tall1 = all1[u] - 1;
                int tall2 = all2[u] - (f[v][1] || (!f[v][2] && !f[v][3]));
                if (tcnt0) {
                    tf[0] = true;
                } else {
                    if (tall1 == tall2) {
                        tf[1] = true;
                    } else {
                        if (tcnt3 % 2 == 1) {
                            tf[3] = true;
                        } else {
                            if (tcnt2) {
                                tf[2] = true;
                            }
                        }
                    }
                }
                vc[v].emplace_back(tf[0] || tf[3]);
                hav[v] += tf[0] || tf[3];
            }
            redfs(v);
        }
    } else {
        int cnt = vc[u].size();
        for (int i = 0; i < cnt - 1; ++i)
            vc[u].emplace_back(vc[u][i]);
        int m = vc[u].size();
        vector<int> nxt(m), pre(m);
        for (int i = m - 1; ~i; --i)
            nxt[i] = vc[u][i] ? i : i == m - 1 ? m : nxt[i + 1];
        for (int i = 0; i < m; ++i)
            pre[i] = vc[u][i] ? i : i == 0 ? -1 : pre[i - 1];
        int pos = 0;
        for (int i = T.head[u]; i; i = T.edge[i].nxt) {
            int v = T.edge[i].v;
            
            bool tf[4] = { false, false, false, false };
            int thav = 0;
            
            if (v < 0) {
                v = -v;
                tf[0] = g[u][0];
                tf[1] = g[u][1];
                tf[2] = g[u][2];
                tf[3] = g[u][3];
            } else {
                thav = hav[u] - (f[v][0] || f[v][3]);
                
                if (thav) {
                    bool win = false;
                    win |= (nxt[pos + 1] - pos - 1) % 2 == 1;
                    win |= (pos + cnt - 1 - pre[pos + cnt - 1]) % 2 == 1;
                    if (win) {
                        tf[0] = true;
                    } else {
                        tf[1] = true;
                    }
                } else {
                    if (cnt % 2 == 1) {
                        tf[3] = true;
                    } else {
                        tf[2] = true;
                    }
                }
            }
            
            cnt3[v] += tf[3];
            cnt2[v] += tf[2];
            cnt0[v] += tf[0];
            ++all1[v];
            all2[v] += tf[1] || (!tf[2] && !tf[3]);
            if (cnt0[v]) {
                g[v][0] = true;
            } else {
                if (all1[v] == all2[v]) {
                    g[v][1] = true;
                } else {
                    if (cnt3[v] % 2 == 1) {
                        g[v][3] = true;
                    } else {
                        if (cnt2[v]) {
                            g[v][2] = true;
                        }
                    }
                }
            }
            redfs(v);
            
            ++pos;
        }
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1, u, v; i <= m; ++i) {
        scanf("%d%d", &u, &v);
        G.add(u, v), G.add(v, u);
    }
    $build::tarjan(1, 0);
    dfs(1);
    g[1][0] = f[1][0], g[1][1] = f[1][1];
    g[1][2] = f[1][2], g[1][3] = f[1][3];
    redfs(1);
    for (int i = 1; i <= n; ++i) {
        puts(g[i][0] || g[i][3] ? "1" : "2");
    }
    return 0;
}
posted @ 2025-07-19 19:14  XuYueming  阅读(48)  评论(0)    收藏  举报