【LOJ 2788】管道(树上差分)

管道

题目链接:LOJ 2788

题目大意

给你一个 n 个点 m 条边的无向图不一定连通,把每个连通块看做子图,求每一个子图的桥。
n 1e5 m 6e6 空间 16 MB

思路

发现存不下所有的边,但是能存点。
那考虑能不能在一棵生成树上面搞。

那对于一条非树边,它会让树上的一条路径上的边都不会是桥边。
那你把所有非树边都这么限制一下,最后还没有被否决的就是桥边了。

那至于怎么限制,一个想法是树上差分,那就需要找到 LCA。
那你可以二次读入数据,第一次先把生成树建出来,然后弄个树链剖分或者倍增预处理,然后第二次处理非树边,求出 LCA 打标记。
然后最后从下往上,把标记加上去,然后每个点判断一下它跟它父亲的边就行了。

那这样的复杂度是 \(O(n+q\log n)\),大概有 \(60\) 左右,而且二次读入很慢。
(不过开 O2,再加上加速 cin cout 的 ios::sync_with_stdio(false)勉强能在 LOJ 卡过)


考虑瓶颈是啥,是你非树边太多了,一个一个打标记都不太行。
而且需要预处理才能得到 LCA。
(虽然有个 \(O(n\log n-1)\) LCA,但是似乎有更好的方法)

注意到非树边这么多,比树边还多,真的全有用吗。
会发现当非树边出现环之类的时候,你可以在环上任意选一条边不要,它的贡献是其他边能拼凑出来的。
也就是说,你其实只需要保留非树边的一棵生成树。

那这个时候非树边的数量也降到了 \(O(n)\),我们甚至就可以直接在第一次读入的时候存下所有的非树边,也就不需要二次读入,复杂度也降到了可以通过的 \(O(n\alpha(n)\log n)\)
(当然你可以用 \(O(n\log n-1)\) 的 LCA 做到 \(O(n\alpha(n))\)

代码

不需要二次读入数据

#include<queue>
#include<cstdio>
#include<vector>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 1e5 + 100;
int n, m, fa[N], x, y, Fa[N], le[N], KK, a[N], lca;
vector <pair<int, int> > out;
struct node {
	int to, nxt;
}e[N << 1];

void add(int x, int y) {
	e[++KK] = (node){y, le[x]}; le[x] = KK;
	e[++KK] = (node){x, le[y]}; le[y] = KK;
}

int find(int now) {
	if (fa[now] == now) return now;
	return fa[now] = find(fa[now]);
}

int Find(int now) {
	if (Fa[now] == now) return now;
	return Fa[now] = Find(Fa[now]);
}

struct SLPF {
	int fa[N], son[N], top[N], deg[N], dfn[N];
	
	void dfs0(int now, int father) {
		top[now] = 1; deg[now] = deg[father] + 1; fa[now] = father;
		for (int i = le[now]; i; i = e[i].nxt)
			if (e[i].to != father) {
				dfs0(e[i].to, now); top[now] += top[e[i].to];
				if (top[e[i].to] > top[son[now]]) son[now] = e[i].to;
			}
	}
	
	void dfs1(int now, int father) {
		dfn[now] = ++dfn[0];
		if (son[now]) {
			top[son[now]] = top[now]; dfs1(son[now], now);
		}
		for (int i = le[now]; i; i = e[i].nxt)
			if (e[i].to != father && e[i].to != son[now]) {
				top[e[i].to] = e[i].to; dfs1(e[i].to, now);
			}
	}
	
	int LCA(int x, int y) {
		while (top[x] != top[y]) {
			if (deg[top[x]] < deg[top[y]]) swap(x, y);
			x = fa[top[x]];
		}
		if (deg[x] > deg[y]) swap(x, y);
		return x;
	}
	
	void find(int now, int father) {
		dfn[now] = 1;
		for (int i = le[now]; i; i = e[i].nxt)
			if (e[i].to != father) {
				find(e[i].to, now);
				a[now] += a[e[i].to];
			}
		if (!a[now] && father) printf("%d %d\n", now, father);
	}
}T;

int re; char c;
int read() {
	re = 0; c = getchar();
	while (c < '0' || c > '9') c = getchar();
	while (c >= '0' && c <= '9') {
		re = (re << 3) + (re << 1) + c - '0';
		c = getchar();
	}
	return re;
}

int main() {
//	freopen("read.txt", "r", stdin);
	
	n = read(); m = read();
	for (int i = 1; i <= n; i++) fa[i] = Fa[i] = i;
	for (int i = 1; i <= m; i++) {
		x = read(); y = read();
		if (find(x) != find(y)) {
			add(x, y);
			fa[find(x)] = find(y);
		}
		else if (Find(x) != Find(y)) {
			Fa[Find(x)] = Find(y);
			out.push_back(make_pair(x, y));
		}
	}
	
	for (int i = 1; i <= n; i++) if (!T.top[i]) T.dfs0(i, 0);
	for (int i = 1; i <= n; i++) if (!T.dfn[i]) T.top[i] = i, T.dfs1(i, 0);
	
	for (int i = 0; i < out.size(); i++) {
		x = out[i].first; y = out[i].second;
		lca = T.LCA(x, y);
		a[x]++; a[y]++; a[lca] -= 2;
	}
	
	for (int i = 1; i <= n; i++) T.dfn[i] = 0;
	for (int i = 1; i <= n; i++) if (!T.dfn[i]) T.find(i, 0);
	
	return 0;
}

勉强卡过的二次读入数据

#include<cstdio>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 1e5 + 100;
int n, m, fa[N], x, y, lca, a[N];

int find(int now) {
	if (fa[now] == now) return now;
	return fa[now] = find(fa[now]);
}

struct SLPF {
	struct node {
		int to, nxt;
	}e[N << 1];
	int fa[N], son[N], top[N], le[N], KK, deg[N], dfn[N];
	
	void add(int x, int y) {
		e[++KK] = (node){y, le[x]}; le[x] = KK;
		e[++KK] = (node){x, le[y]}; le[y] = KK;
	}
	
	void dfs0(int now, int father) {
		top[now] = 1; deg[now] = deg[father] + 1; fa[now] = father;
		for (int i = le[now]; i; i = e[i].nxt)
			if (e[i].to != father) {
				dfs0(e[i].to, now); top[now] += top[e[i].to];
				if (top[e[i].to] > top[son[now]]) son[now] = e[i].to;
			}
	}
	
	void dfs1(int now, int father) {
		dfn[now] = ++dfn[0];
		if (son[now]) {
			top[son[now]] = top[now]; dfs1(son[now], now);
		}
		for (int i = le[now]; i; i = e[i].nxt)
			if (e[i].to != father && e[i].to != son[now]) {
				top[e[i].to] = e[i].to; dfs1(e[i].to, now);
			}
	}
	
	int LCA(int x, int y) {
		while (top[x] != top[y]) {
			if (deg[top[x]] < deg[top[y]]) swap(x, y);
			x = fa[top[x]];
		}
		if (deg[x] > deg[y]) swap(x, y);
		return x;
	}
	
	void find(int now, int father) {
		dfn[now] = 1;
		for (int i = le[now]; i; i = e[i].nxt)
			if (e[i].to != father) {
				find(e[i].to, now);
				a[now] += a[e[i].to];
			}
		if (!a[now] && father) printf("%d %d\n", now, father);
	}
}T;

int main() {
//	freopen("read.txt", "r", stdin);
    ios::sync_with_stdio(false);
	
	cin >> n >> m;
	for (int i = 1; i <= n; i++) fa[i] = i;
	for (int i = 1; i <= m; i++) {
		cin >> x >> y;
		if (find(x) != find(y)) {
			T.add(x, y);
			fa[find(x)] = find(y);
		}
	}
	
	for (int i = 1; i <= n; i++) if (!T.top[i]) T.dfs0(i, 0);
	for (int i = 1; i <= n; i++) if (!T.dfn[i]) T.top[i] = i, T.dfs1(i, 0);
	
	cin.clear(); cin.seekg(0, ios::beg);
	cin >> n >> m;
	for (int i = 1; i <= n; i++) fa[i] = i;
	for (int i = 1; i <= m; i++) {
		cin >> x >> y;
		if (find(x) != find(y)) {
			fa[find(x)] = find(y);
		}
		else {
			lca = T.LCA(x, y);
			a[x]++; a[y]++; a[lca] -= 2;
		}
	}
	
	for (int i = 1; i <= n; i++) T.dfn[i] = 0;
	for (int i = 1; i <= n; i++) if (!T.dfn[i]) T.find(i, 0);
	
	return 0;
}
posted @ 2023-03-13 10:30  あおいSakura  阅读(14)  评论(0编辑  收藏  举报