Loading

P2542 [AHOI2005] 航线规划 (trick+树剖)

P2542 [AHOI2005] 航线规划

trick+树剖

首先删边操作困难,考虑倒序处理

发现题目中的关键性质:无论航线如何被破坏,任意时刻任意两个星球都能够相互到达。在整个数据中,任意两个星球之间最多只可能存在一条直接的航线。

这说明无论何时图都是连通的,那么我们完全可以建一棵树,再考虑加边对树的影响

首先如果是树,那么两点路径唯一,关键航线数量即为路径长度。若此时加了一条边,一定会在树上形成一个环,此时环上任意一条边再断开对图的连通性不会有任何影响,那么环上的边不可能为关键航线了

这个操作其实就是路径覆盖 \(0\),树剖维护即可。

建树的操作我是用并查集维护的。

复杂度 \(O(n\log n)\)

#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define fi first
#define se second
#define pb push_back

typedef long long i64;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
const int N = 30010, M = 1e6;
int n, m, k, cnt, q;
int fa[N], vis[M], h[N], ans[M];
std::map<pii, int> mp;
std::vector<pii> E;
std::vector<std::array<int, 3>> G;
int find(int x) {
	if(x != fa[x]) fa[x] = find(fa[x]);
	return fa[x];
}
struct node {
	int to, nxt;
} e[N << 1];
void add(int u, int v) {
	e[++cnt].to = v, e[cnt].nxt = h[u], h[u] = cnt;
}
int num;
int sz[N], dep[N], id[N], son[N], anc[N], top[N];
void dfs1(int u, int fa) {
	sz[u] = 1;
	dep[u] = dep[fa] + 1;
	anc[u] = fa;
	for(int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].to;
		if(v == fa) continue;
		dfs1(v, u);
		sz[u] += sz[v];
		if(sz[v] > sz[son[u]]) son[u] = v;
	} 
}	
void dfs2(int u, int topf) {
	top[u] = topf;
	id[u] = ++num;
	if(!son[u]) return;
	dfs2(son[u], topf);
	for(int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].to;
		if(v == anc[u] || v == son[u]) continue;
		dfs2(v, v);
	}
}
struct seg {
	int v, lzy;
} t[N << 2];
void pushup(int u) {t[u].v = t[u << 1].v + t[u << 1 | 1].v;}
void pd(int u) {
	if(!t[u].lzy) return;
	t[u << 1].v = t[u << 1 | 1].v = 0;
	t[u << 1].lzy = t[u << 1 | 1].lzy = 1;
	t[u].lzy = 0;
}
void build(int u, int l, int r) {
	if(l == r) {
		if(l != 1) t[u].v = 1;
		return;
	}
	int mid = (l + r) >> 1;
	build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
	pushup(u);
}
void update(int u, int l, int r, int L, int R) {
	if(L <= l && r <= R) {
		t[u].v = 0, t[u].lzy = 1;
		return;
	}
	int mid = (l + r) >> 1; pd(u);
	if(L <= mid) update(u << 1, l, mid, L, R);
	if(R > mid) update(u << 1 | 1, mid + 1, r, L, R);
	pushup(u);
}
int query(int u, int l, int r, int L, int R) {
	if(L <= l && r <= R) return t[u].v;
	int mid = (l + r) >> 1, ret = 0; pd(u);
	if(L <= mid) ret += query(u << 1, l, mid, L, R);
	if(R > mid) ret += query(u << 1 | 1, mid + 1, r, L, R);
	return ret;
}
int qry(int u, int v) {
	int ret = 0;
	while(top[u] != top[v]) {
		if(dep[top[u]] < dep[top[v]]) std::swap(u, v);
		ret += query(1, 1, n, id[top[u]], id[u]);
		u = anc[top[u]];
	}
	if(dep[u] > dep[v]) std::swap(u, v);
	ret += query(1, 1, n, id[u] + 1, id[v]);
	return ret;
}
void mdf(int u, int v) {
	while(top[u] != top[v]) {
		if(dep[top[u]] < dep[top[v]]) std::swap(u, v);
		update(1, 1, n, id[top[u]], id[u]);
		u = anc[top[u]];
	}
	if(dep[u] > dep[v]) std::swap(u, v);
	update(1, 1, n, id[u] + 1, id[v]);
}
void Solve() {
	std::cin >> n >> m;
	E.pb({0, 0}), G.pb({0, 0});
	for(int i = 1; i <= n; i++) fa[i] = i;
	for(int i = 1; i <= m; i++) {
		int u, v;
		std::cin >> u >> v;
		E.pb({u, v});
		mp[{u, v}] = i;
	}
	while(1) {
		int op;
		std::cin >> op;
		if(op == -1) break;
		int u, v;
		std::cin >> u >> v;
		if(op == 1) G.pb({++q, u, v});
		else {
			G.pb({0, u, v});
			E[mp[{u, v}]] = {0, 0};
		}
		k++;
	}
	for(auto x : E) {
		if(x.fi) G.pb({0, x.fi, x.se});
	}
	std::reverse(G.begin() + 1, G.end());
	int g = G.size();
	for(int i = 1; i <= g - k - 1; i++) {
		int x = find(G[i][1]), y = find(G[i][2]);
		if(x == y) continue;
		vis[i] = 1, fa[x] = y;
		add(G[i][1], G[i][2]), add(G[i][2], G[i][1]); 
 	}
 	dfs1(1, 0), dfs2(1, 1), build(1, 1, n);
	for(int i = 1; i < g; i++) {
		if(vis[i]) continue;
 		if(G[i][0]) {
 			ans[G[i][0]] = qry(G[i][1], G[i][2]);
 		} else {
 			mdf(G[i][1], G[i][2]);
 		}
 	}
 	for(int i = 1; i <= q; i++) std::cout << ans[i] << "\n";
}
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
	Solve();

	return 0;
}
posted @ 2024-04-06 21:40  Fire_Raku  阅读(2)  评论(0编辑  收藏  举报