加载中…

题解:P12737 [POI 2016 R2] 可变方向道路 Vari-directional Streets

作为全洛谷第一个通过的人,我很荣幸。

首先,我们先读清题意。根据样例,我们可知,若一个点在奇数日和偶数日所能走到的点的并集为全集,则该点需被统计。

读完题,我最直接的想法是缩点。因为强连通分量对我们统计答案是无影响的。

接着,我们有可能陷入一个误区,就是答案为所有入度为零的点到出度为零的点的路径上必经的点。这的确都是答案。于是我最开始就写了一个支配树,用于寻找必经点。

支配树的性质是任意一个点 \(u\),从 \(u\) 出发到根节点 \(root\) 的路径上,都是必经点。

然后我们就会 WA 三个点,喜提 \(44pts\)

怎么回事呢?

我们给出一组小 hack:

3 3
1 2
2 3
1 3

正确输出应是:

3
1 2 3

但是我们发现 \(2\) 并不是必经点。

于是我们就会对原做法进行缝补。

已知,我们求的点是答案的自集,那么怎么找到其它的答案呢?

有一个东西叫做拓扑覆盖。

我们在缩点之后的 DAG 上判断,若该 SCC 可以被所有拓扑序小于它的点到达,也可以到达所有拓扑序大于该 SCC 的点。

我们可以记录后继拓扑序最小值和前驱拓扑序最大值。

对于 \(u = 1,2,3,\dots,cnt\),若有 \(premax[u - 1] \le i\),且 \(sufmin[u + 1] \ge i\),则该 SCC 成立。

注意,这里的左右是只该 SCC 的入边和出边。

这样我们就保证了没有边可以跳过我们所讨论的 SCC。

下面给出代码:

#include<bits/stdc++.h>
using namespace std;
int n, m, dfn[500005], low[500005], dfsid, cnt, num[500005], siz[500005], in[500005], ou[500005], s, t, du[500005], f[500005][20], deep[500005], fa[500005], pos[500005], pre[500005], nxt[500005], premx[500005], sufmn[500005], at[500005];
bool mark[500005], markbelong[500005];
stack<int> st;
vector<int> v[500005], v1[500005], v2[500005], topo, must;
void dfs(int u) {
	dfn[u] = low[u] = ++dfsid;
	st.push(u);
	mark[u] = true;
	for (auto i : v[u]) {
		if (!dfn[i]) {
			dfs(i);
			low[u] = min(low[u], low[i]);
		} else if (mark[i]) {
			low[u] = min(low[u], dfn[i]);
		}
	}
	if (dfn[u] == low[u]) {
		++cnt;
		while (st.top() != u) {
			num[st.top()] = cnt;
			mark[st.top()] = false;
			st.pop();
		}
		num[st.top()] = cnt;
		mark[st.top()] = false;
		st.pop();
	}
}
int get_lca(int x, int y) {
	if (deep[x] < deep[y]) {
		swap(x, y);
	}
	for (int i = 19; i >= 0; i --) {
		if (deep[f[x][i]] >= deep[y]) {
			x = f[x][i];
		}
	}
	if (x == y) {
		return x;
	}
	for (int i = 19; i >= 0; i --) {
		if (f[x][i] != f[y][i]) {
			x = f[x][i];
			y = f[y][i];
		}
	}
	return f[x][0];
}
signed main() {
	// freopen("P12737_21.in", "r", stdin);
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= m; i ++) {
		int x, y;
		cin >> x >> y;
		v[x].push_back(y);
	}
	for (int i = 1; i <= n; i ++) {
		if (!dfn[i]) {
			dfs(i);
		}
	}
	if (cnt == 1) {
		cout << n << "\n";
		for (int i = 1; i <= n; i ++) {
			cout << i << " ";
		}
		cout << "\n";
		return 0;
	}
	for (int i = 1; i <= n; i ++) {
		for (auto j : v[i]) {
			if (num[i] != num[j]) {
				v1[num[i]].push_back(num[j]);
				v2[num[j]].push_back(num[i]);
				in[num[j]] ++;
				ou[num[i]] ++;
			}
		}
	}
	s = cnt + 1;
	t = cnt + 2;
	for (int i = 1; i <= cnt; i ++) {
		if (!in[i]) {
			v1[s].push_back(i);
			v2[i].push_back(s);
		}
	}
	for (int i = 1; i <= cnt; i ++) {
		if (!ou[i]) {
			v1[i].push_back(t);
			v2[t].push_back(i);
		}
	}
	for (int i = 1; i <= cnt + 2; i ++) {
		for (auto j : v1[i]) {
			du[j] ++;
		}
	}
	queue<int> q;
	q.push(s);
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		topo.push_back(u);
		pos[u] = topo.size();
		for (auto i : v1[u]) {
			du[i] --;
			if (!du[i]) {
				q.push(i);
			}
		}
	}
	memset(fa, -1, sizeof(fa));
	fa[s] = s;
	for (int i = 0; i <= 19; i ++) {
		f[s][i] = s;
	}
	bool flag = true;
	for (auto i : topo) {
		if (flag) {
			flag = false;
			continue;
		}
		int father = -1;
		for (auto j : v2[i]) {
			father = j;
			break;
		}
		for (auto j : v2[i]) {
			father = get_lca(father, j);
		}
		fa[i] = father;
		deep[i] = deep[father] + 1;
		f[i][0] = father;
		for (int j = 1; j <= 19; j ++) {
			f[i][j] = f[f[i][j - 1]][j - 1];
		}
	}
	int now = fa[t];
	while (now != s) {
		must.push_back(now);
		markbelong[now] = true;
		now = fa[now];
	}
	for (int i = 1; i <= cnt; i ++) {
		at[pos[i] - 1] = i;
		pre[i] = 0;
		nxt[i] = cnt + 1;
	}
	for (int i = 1; i <= cnt; i ++) {
		for (auto j : v1[i]) {
			if (1 <= j && j <= cnt) {
				nxt[i] = min(nxt[i], pos[j] - 1);
				pre[j] = max(pre[j], pos[i] - 1);
			}
		}
	}
	premx[0] = 0;
	for (int i = 1; i <= cnt; i ++) {
		int u = at[i];
		premx[i] = max(premx[i - 1], nxt[u]);
	}
	sufmn[cnt + 1] = cnt + 1;
	for (int i = cnt; i >= 1; i --) {
		int u = at[i];
		sufmn[i] = min(sufmn[i + 1], pre[u]);
	}
	for (int i = 1; i <= cnt; i ++) {
		int u = at[i];
		bool flag1 = (premx[i - 1] <= i), flag2 = (sufmn[i + 1] >= i);
		if (flag1 && flag2) {
			markbelong[u] = true;
		}
	}
	vector<int> ans;
	for (int i = 1; i <= n; i ++) {
		if (markbelong[num[i]]) {
			ans.push_back(i);
		}
	}
	cout << ans.size() << "\n";
	for (auto i : ans) {
		cout << i << " ";
	}
	return 0;
}
posted @ 2026-07-17 11:37  xuyifei0302  阅读(16)  评论(0)    收藏  举报
🌙