LY1112 [ 20230227 CQYC模拟赛 T3 ] 强连通

发布时间 2024-01-11 20:03:46作者: cxqghzj

题意

给定一张有向图,问你反转一条边后是否对 \(scc\) 有变化。

\(n \le 1500, m \le 10^6\)

Sol

先对图跑一边 \(tarjan\),考虑对每条边进行分讨。

  • 在同一强连通分量里。如果反转后依然有一条 \(u \to v\) 的路径,那么 \(scc\) 不变,否则变多。
  • \(DAG\) 的边上。如果反转后依然有一条 \(u \to v\) 的路径,\(scc\) 变少,因为 \(u \to v\) 将两个 \(scc\) 连起来了,否则不变。

所以,问题转为询问 \(u \to v\) 的边去掉后是否有一条 \(u \to v\) 的路径。

直接暴力 \(bfs\) 固然是 \(O(m \times (n + m))\) 的。

注意到 \(n\) 很小,所以想到用 \(bitset\) 优化 \(bfs\) 求两点联通性。

我们设 \(g_u\) 表示 \(u\) 能到达的所有节点。

考虑 \(u\) 的所有出点,设为 \(V_i\)

不难发现当前 \(u, v\) 联通,当且仅当存在一个 \(V_i\) 有一条 \(V_i \to v\) 的路径,那么求答案就变为求一个 \([V_1, V_{i - 1}]\) 的前缀,以及 \([V_{i + 1}, V_{|V|}]\) 的后缀的 \(g\) 全部或起来。

那么每次跑 \(bfs\) 就不需要全部清空了,直接暴力加点跑就行。

\(bfs\) 的复杂度为 \(\frac{n ^ 2}{\omega}\)

每个点的所有出点跑一次 \(bfs\)

总时间复杂度:\(O(\frac{n ^ 3}{\omega} + \frac{n \times m}{\omega})\)

Code

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <array>
#include <stack>
#include <queue>
#include <bitset>
#include <vector>
#define pii pair <int, int>
using namespace std;
#ifdef ONLINE_JUDGE

#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf[1 << 23], *p1 = buf, *p2 = buf, ubuf[1 << 23], *u = ubuf;

#endif
int read() {
	int p = 0, flg = 1;
	char c = getchar();
	while (c < '0' || c > '9') {
		if (c == '-') flg = -1;
		c = getchar();
	}
	while (c >= '0' && c <= '9') {
		p = p * 10 + c - '0';
		c = getchar();
	}
	return p * flg;
}
void write(int x) {
	if (x < 0) {
		x = -x;
		putchar('-');
	}
	if (x > 9) {
		write(x / 10);
	}
	putchar(x % 10 + '0');
}
#define fi first
#define se second

const int N = 1505, M = 1e6 + 5;

namespace G {

array <int, N> fir;
array <int, M> nex, to, len;

int cnt = 1;

void add(int x, int y, int z) {
	cnt++;
	nex[cnt] = fir[x];
	to[cnt] = y;
	len[cnt] = z;
	fir[x] = cnt;
}

}

namespace Tarjan {

array <int, N> dfn, low;
bitset <N> mark, vis;
stack <int> stk;

int cnt = 1, scc = 0;

array <int, N> col;

void tarjan(int x) {
	dfn[x] = low[x] = cnt++;
	stk.push(x);
	mark[x] = vis[x] = 1;
	for (int i = G::fir[x]; i; i = G::nex[i]) {
		if (!dfn[G::to[i]])
			tarjan(G::to[i]), low[x] = min(low[x], low[G::to[i]]);
		else if (mark[G::to[i]])
			low[x] = min(low[x], dfn[G::to[i]]);
	}
	if (dfn[x] != low[x]) return;
	scc++;
	while (stk.top() != x) {
		col[stk.top()] = scc;
		mark[stk.top()] = 0;
		stk.pop();
	}
	col[stk.top()] = scc;
	mark[stk.top()] = 0;
	stk.pop();
}

}

array <bitset <N>, N> f, g;

bitset <N> vis, tp;
queue <int> q;

void bfs(int x, int n) {
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		if (u == x) continue;
		tp = (g[u] & vis) ^ g[u];
		for (int i = tp._Find_first(); i <= n; i = tp._Find_next(i))
			vis[i] = 1, q.push(i);
	}
}

bitset <N * N> ans;
vector <pii> isl;

int main() {
	/* freopen("scc.in", "r", stdin); */
	/* freopen("scc.out", "w", stdout); */
	int n = read(), m = read();
	for (int i = 1; i <= m; i++) {
		int x = read(), y = read();
		G::add(x, y, i), g[x][y] = 1;
	}
	for (int i = 1; i <= n; i++)
		if (!Tarjan::dfn[i]) Tarjan::tarjan(i);
	/* for (int i = 1; i <= n; i++) */
		/* write(Tarjan::col[i]), putchar(32); */
	/* puts(""); */
	for (int i = 1; i <= n; i++) {
		vis = 0;
		isl.clear();
		for (int j = G::fir[i]; j; j = G::nex[j])
			isl.push_back(make_pair(G::to[j], G::len[j]));
		for (int j = 0; j < (int)isl.size(); j++) {
			if (!vis[isl[j].fi]) q.push(isl[j].fi), bfs(i, n), f[j + 1] = vis;
			else f[j + 1] = f[j];
		}
		vis = 0;
		for (int j = isl.size() - 1; ~j; j--) {
			ans[isl[j].se] = (vis.test(isl[j].fi) | f[j].test(isl[j].fi)) ^ (Tarjan::col[i] == Tarjan::col[isl[j].fi]);
			if (!vis[isl[j].fi]) q.push(isl[j].fi), bfs(i, n);
		}
	}
	for (int i = 1; i <= m; i++)
		write(ans[i]), puts("");
	return 0;
}