[洛谷P5966] [BZOJ4344] [POI2016] Hydrorozgrywka
更好的阅读体验
题解
建出原图的圆方树。由于原图无重边,不妨把桥看作二元环建树,这样圆点只与方点直接相连。
圆方树定某一圆点为根后,若点
考虑如何判断棋子在点
这启发我们在圆方树上进行关于子树(即子仙人掌)信息的树形 dp。
在一切的之前先想象一下圆方树上 dp 的过程:方点的转移是合并若干个根节点串成环的子仙人掌的信息,圆点的转移是合并若干个共且仅共根节点的子仙人掌的信息。
设计 dp 状态
最直接的设计 dp 状态的方法是
我们想象子仙人掌的根节点向外部连出一条边。
-
如果先手必败,那么先手会直接走那条边。
-
如果先手必胜并且可以逼迫后手最终停留在非根节点,先手会进行这个必胜的游戏。
-
否则,先手可以直接选择走那条边,也可以选择逼迫后手走那条边。
我们把这三种情况分别对应
进行 dp 转移
下文中点
方点
如果点
否则,考虑在环上按照某个方向走一圈的过程:
如果我们碰上了一个
如果我们碰上了一个
如果我们碰上了一个 选择是否改变先后手,因此当前玩家必胜。
任何一个公平组合游戏中,如果你能在某一时刻有两种进入完全相同的局面、但先后手不同的决策,那么你是必胜的(这里的决策相当于把有向图上的很多已经确定会走的步打包成一步)。
因此找到环上向左走和向右走遇到的第一个
特殊情况是
圆点
如果
否则,如果
换根
综上,我们容易得到一个
2025.7.19 修正代码换根 dp 部分的错误实现
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using i128 = __int128;
using u128 = unsigned __int128;
template <typename T>
void chkmax(T &x, const T &y) {
if (x < y) x = y;
}
template <typename T>
void chkmin(T &x, const T &y) {
if (y < x) x = y;
}
constexpr int MAXN = 5e5 + 10;
int n, dfn[MAXN], low[MAXN], dfc, stk[MAXN], tp, num, dwn[MAXN * 2];
int cnt1[MAXN], cnt2[MAXN], up[MAXN * 2], all[MAXN], posl[MAXN * 2],
posr[MAXN * 2];
vector<int> og[MAXN], g[MAXN * 2];
void tarjan(int u) {
dfn[u] = low[u] = ++dfc;
stk[++tp] = u;
for (auto v : og[u]) {
if (!dfn[v]) {
tarjan(v);
chkmin(low[u], low[v]);
if (low[v] == dfn[u]) {
g[u].emplace_back(++num);
while (true) {
int t = stk[tp--];
g[num].emplace_back(t);
if (t == v) break;
}
}
} else {
chkmin(low[u], dfn[v]);
}
}
}
void dfs1(int u) {
for (auto v : g[u]) dfs1(v);
if (u <= n) {
for (auto v : g[u]) {
if (dwn[v] == 1) {
++cnt1[u];
} else if (dwn[v] == 2) {
++cnt2[u];
}
}
if (cnt1[u]) {
dwn[u] = 1;
} else {
dwn[u] = (cnt2[u] & 1 ? 2 : 0);
}
} else {
if (g[u].size() == 1) {
dwn[u] = !dwn[g[u][0]];
} else {
posl[u] =
find_if(g[u].begin(), g[u].end(), [](int x) { return dwn[x]; }) -
g[u].begin();
if (posl[u] == (int)g[u].size()) {
dwn[u] = (g[u].size() & 1 ? 0 : 2);
} else {
posr[u] =
find_if(g[u].rbegin(), g[u].rend(), [](int x) { return dwn[x]; }) -
g[u].rbegin();
dwn[u] = (posl[u] & 1) || (posr[u] & 1);
}
}
}
}
void dfs2(int u) {
if (u <= n) {
if (cnt1[u] + (up[u] == 1)) {
all[u] = 1;
} else {
all[u] = ((cnt2[u] + (up[u] == 2)) & 1 ? 2 : 0);
}
for (auto v : g[u]) {
if (cnt1[u] + (up[u] == 1) - (dwn[v] == 1)) {
up[v] = 1;
} else {
up[v] = ((cnt2[u] + (up[u] == 2) - (dwn[v] == 2)) & 1 ? 2 : 0);
}
dfs2(v);
}
} else {
if (g[u].size() == 1) {
up[g[u][0]] = !up[u];
dfs2(g[u][0]);
} else {
static int nxt[MAXN];
for (int i = g[u].size() - 1, lst = g[u].size(); i >= 0; --i) {
nxt[i] = lst;
if (dwn[g[u][i]]) lst = i;
}
if (up[u]) posl[u] = -1;
for (int i = 0, v, lst = (up[u] ? -1 : -2); i < (int)g[u].size(); ++i) {
v = g[u][i];
if (lst == -2 && nxt[i] == (int)g[u].size()) {
up[v] = (g[u].size() & 1 ? 0 : 2);
} else if (lst == -2) {
up[v] = !((i + posr[u]) & 1) || !((nxt[i] - i) & 1);
} else if (nxt[i] == (int)g[u].size()) {
up[v] = !((i - lst) & 1) || ((g[u].size() - i + posl[u]) & 1);
} else {
up[v] = !((i - lst) & 1) || !((nxt[i] - i) & 1);
}
if (dwn[v]) lst = i;
}
for (auto v : g[u]) dfs2(v);
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int m;
cin >> n >> m;
while (m--) {
int u, v;
cin >> u >> v;
og[u].emplace_back(v);
og[v].emplace_back(u);
}
num = n;
tarjan(1);
dfs1(1);
dfs2(1);
for (int i = 1; i <= n; ++i) cout << 2 - (bool)all[i] << "\n";
return 0;
}
/*
g++ Hydro.cpp -o Hydro -std=c++14 -O2 -Wall -Wextra -Wshadow -g
-fsanitize=address,undefined
*/