GAME on cactus | [POI 2016] Hydrorozgrywka 题解
前言
题目链接:洛谷、GAME on cactus。
为啥没桥啊,没用到什么性质啊。
为啥没重边啊,没用到什么性质啊。
推荐阅读:《圆方树学习笔记 —— 一种关于点双连通分量的思考方式》。
题意简述
原题无桥,不妨加强。原题无重边,不妨加强。
\(n\) 个点、\(m\) 条边的仙人掌上的博弈游戏。玩家可以选择一条没被经过的边,将棋子从边的一边移动到另一边。无法移动的玩家输。对于每一个点,求出棋子一开始在该点,先手是否有必胜策略。
\(n\leq5\times10^5\)。
题目分析
显然建出圆方树,树形 DP。设 \(f_u=0/1\) 表示先手从 \(u\) 开始,只考虑 \(u\) 的子仙人掌,是否必胜 / 必败。
但是这个状态有缺陷,因为有可能绕了一圈回到 \(u\),继续往子仙人掌以外走。总共有以下四种情况:
- 先手如果往子仙人掌里走必败,即被堵在了 \(u\) 的某一个子孙中;
- 先手如果往子仙人掌里走必胜,后手被堵在了 \(u\) 的某一个子孙中;
- 先手如果往子仙人掌里走,回到 \(u\),轮到后手,且不会再往子仙人掌里走。
- 先手如果往子仙人掌里走,回到 \(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}\) 就是必胜的。转移的话,分情况讨论。
-
\(u\) 是方点
-
如果这个点双不是个环(即两点被桥连接)
如果 \(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}\);
不存在其他情况。
-
如果这个点双是个环
一定会按环序依次经过环上的点,不妨先考虑顺时针。显然,一旦经过一个子仙人掌,这个子仙人掌之后就不会被访问到了。
先考虑是否存在必胜必败的情况。
-
经过一个点 \(f_{v,1}=\text{true}\):
先手显然会跳过这个点,直接走到下一个点。
-
经过一个点 \(f_{v,0}=\text{true}\):
先手显然选择走进这个子仙人掌,必胜。
-
经过一个点 \(f_{v,2}=\text{true}\):
先手走不走无所谓,都会走到下一个点。
-
经过一个点 \(f_{v,3}=\text{true}\):
先手既可以选择走进去,换成后手,走向下一个点,也可以直接走到下一个点。也就是说,先手可以选择是否改变先后手。那么后继状态如果必胜,先手不会改变先后手,否则先手改变先后手,同样必胜。
-
经过一个点 \(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}\)。
-
-
-
\(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;
}
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/18992600。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。

浙公网安备 33010602011771号