10.24 L5-noip训练2 C. game 题解
题意
考虑 \(N\) 个人玩一个游戏,任意两个人之间进行一场游戏(共 \(\frac{N(N-1)}2\) 场),且每场一定能分出胜负。
现在,你需要在其中找到三个人构成“剪刀石头布”局面:三个人 \(A,B,C\) 满足 \(A\) 战胜 \(B\),\(B\) 战胜 \(C\),\(C\) 战胜 \(A\)。
思路
首先将胜负关系建成一张有向图,由胜者向负者连边。
实际上这样的图有一个名字:竞赛图。
这样的有向图满足任意两点之间有且仅有一条边。
现在我们要找出图中的一个三元环。
竞赛图有一个重要的性质:在任意一个环中一定存在三元环。
下面来证明这个性质。
首先,两个点不可能互指,所以图中一定不可能有二元环。
现在考虑一个 \(n\) 元环,结构为 \(1\to2\to3\to\dots\to n\to1\)。
考虑 \(1,2,3\) 有没有可能构成一个三元环。如果 \(3\) 到 \(1\) 没有边,那么 \(1\) 到 \(3\) 一定有边。
于是考虑 \(1,3,4\) 有没有可能构成一个三元环。如果 \(4\) 到 \(1\) 依然没有边,那么 \(1\) 到 \(4\) 一定有边。
同理,考虑 \(1,4,5\),\(1,5,6\),\(\ldots\),最后是 \(1,n-1,n\)。如果中间所有三个一组的点都不构成三元环,那么 \(1,n-1,n\) 一定构成三元环。
所以,竞赛图中的任意一个环中都存在三元环。
那么如何找环呢?
我们在图中跑 DFS,经过的点形成一棵 DFS 树。
如果当前节点 \(u\) 有指向祖先节点 \(v\) 的边(称作返祖边),那么说明 DFS 树上 \(v\) 到 \(u\) 的路径与 \((u,v)\) 这条边构成了一个环。
具体来说,我们可以将当前搜索的子树中的节点存到一个栈 \(st\) 中,并且记录树上节点 \(i\) 在栈中的位置 \(id_i\)。
如果发现一条返祖边 \((u,v)\),那么 \(st[id_v..id_u]\) 即构成一个环,起点是 \(v\)。
将这个环中的所有节点存到数组中,然后像刚才的证明过程那样找出三元环即可。
判断是否是祖先节点:用 \(vis_i\) 来表示节点的状态,\(vis_i=1\) 表示正在搜索,即节点是祖先节点;\(vis_i=0\) 表示还未搜索; \(vis_i=2\) 表示在已经搜索完的子树中,不是祖先节点。每个节点初始时 \(vis\) 为 \(0\),入栈时 \(vis\) 变为 \(1\),出栈时 \(vis\) 变为 \(2\)。
代码
#include <cstdio>
#include <iostream>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define FILENAME "game"
using namespace std;
const int N = 5e3 + 10;
int n;
char c[N];
bool g[N][N];
int vis[N];
bool ok;
int st[N], top, id[N];
int circle[N], cnt;
void getans() {
f(i, 2, cnt) {
if (g[circle[i]][circle[1]]) {
cout << circle[1] << ' ' << circle[i - 1] << ' ' << circle[i] << '\n';
break;
}
}
}
void dfs(int u) {
vis[u] = 1;
st[++top] = u;
id[u] = top;
f(v, 1, n) if (g[u][v]) {
if (v == u) continue;
if (vis[v] == 1) {
ok = true;
f(i, id[v], id[u])
circle[++cnt] = st[i];
getans();
return;
} else if (!vis[v]) {
dfs(v);
if (ok) return;
}
}
vis[u] = 2;
--top;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
// freopen(FILENAME".in", "r", stdin);
// freopen(FILENAME".out", "w", stdout);
scanf("%d", &n);
f(i, 1, n) {
scanf("%s", c + 1);
f(j, 1, n)
if (c[j] == '1')
g[i][j] = 1;
}
f(i, 1, n) {
if (!vis[i]) dfs(i);
if (ok) break;
}
if (!ok) cout << "-1\n";
return 0;
}

浙公网安备 33010602011771号