10.24 L5-noip训练2 C. game 题解

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;
}
posted @ 2022-11-06 21:12  f2021ljh  阅读(9)  评论(0)    收藏  举报