1. 题目链接:P2542 [AHOI2005] 航线规划
思路: L C T LCT LCT 加并查集解决,时间复杂度 O ( n l o g n ) O(nlog\ n) O(nlog n) 。我们发现正序删边是不好来维护连通性的,基于正难则反原则,我们可以离线处理,逆序完成操作。显然,每个点可以代表一个双连通分量,查询就是链的长度减一,也就是桥的数量。连接一条边,如果在 L C T LCT LCT 中还没连通就 l i n k link link ,如果连通了,这里会出现一个环,然后我们暴力缩点,可以把当前辅助树(也就是并查集形成的树)的根结点当作集合的标志结点,之后 d f s dfs dfs 整个辅助树,把链上的其他点的并查集暴力改成这个标志结点,最后再断开标志节点与子树的连接,也就是标志结点表示了整棵子树。总的暴力修改次数不会超过 n l o g n nlog\ n nlog n 次。同时要注意 a c c e s s access access 的时候要更新 x x x 为 F i n d ( f [ x ] ) Find(f[x]) Find(f[x]) 。
#include <bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define re register
#define endl '\n'
using namespace std;
const int MOD = 998244353;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
const double Pi = acos(-1.0);
const int N = 3e4 + 5, M = 1e5 + 5;
#define ls(x) (ch[x][0])
#define rs(x) (ch[x][1])
int fa[N], ch[N][2], siz[N];
int f[N], a[M], b[M];
int op[M], ans[M];
bool rev[N], vis[M];
struct node {
int x, y;
bool operator < (const node& r) const {
return x < r.x || (x == r.x && y < r.y);
}
}e[M];
inline int ident (int x, int y) { return rs(y) == x; }
inline int find(int x) {
if (x == f[x]) return x;
return f[x] = find(f[x]);
}
inline bool nroot (int x) {
return ls(fa[x]) == x || rs(fa[x]) == x;
}
inline void pushup (int x) {
siz[x] = siz[ls(x)] + siz[rs(x)] + 1;
}
inline void pushrev (int x) {
swap (ls(x), rs(x));
rev[x] ^= 1;
}
inline void pushdown (int x) {
if (rev[x]) {
if (ls(x)) pushrev (ls(x));
if (rs(x)) pushrev (rs(x));
rev[x] = 0;
}
}
inline void rotate (int x) {
int y = fa[x], z = fa[y], k = ident (x, y);
if (nroot (y)) ch[z][ident (y, z)] = x;
ch[y][k] = ch[x][k ^ 1], ch[x][k ^ 1] = y;
if (ch[y][k]) fa[ch[y][k]] = y;
fa[x] = z, fa[y] = x;
pushup (y), pushup (x);
}
int st[N], top;
inline void splay (int x) {
int r = x;
st[top = 1] = r;
while (nroot (r)) st[++ top] = r = fa[r];
while (top) pushdown (st[top --]);
while (nroot (x)) {
int y = fa[x], z = fa[y];
if (nroot (y))
rotate (ident (x, y) ^ ident (y, z) ? x : y);
rotate (x);
}
pushup (x);
}
inline void access (int x) {
for (int y = 0; x; y = x, x = fa[y] = find(fa[x]))
splay (x), rs(x) = y, pushup (x);
}
inline void makeroot (int x) {
access (x);
splay (x);
pushrev (x);
}
inline int findroot (int x) {
access (x), splay (x);
while (ls(x)) pushdown (x), x = ls(x);
splay (x);
return x;
}
inline void split (int x, int y) {
makeroot (y);
access (x);
splay (x);
}
inline void del (int x, int y) {
if (! x) return;
f[x] = y;
del (ls(x), y);
del (rs(x), y);
}
inline void merge (int x, int y) {
if (x == y) return ;
makeroot (x);
if (findroot (y) != x) {
fa[x] = y;
return ;
}
del (rs(x), x);
rs(x) = 0, pushup(x);
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n, m, cnt, x, y, tot;
cin >> n >> m;
for (int i = 1; i <= n; i ++) siz[i] = 1, f[i] = i;
for (int i = 1; i <= m; i ++) {
cin >> x >> y;
if (x > y) swap (x, y);
e[i] = {x, y};
}
sort (e + 1, e + m + 1);
for (cnt = 1; cin >> op[cnt] && op[cnt] != -1; cnt ++) {
cin >> x >> y;
if (! op[cnt]) {
if (x > y) swap (x, y);
vis[lower_bound (e + 1, e + 1 + m, (node){x, y}) - e] = 1;
}
a[cnt] = x, b[cnt] = y;
}
for (int i = 1; i <= m; i ++)
if (! vis[i]) merge (find(e[i].x), find(e[i].y));
for (cnt --, tot = 0; cnt; cnt --) {
x = find(a[cnt]), y = find(b[cnt]);
if (op[cnt]) split (x, y), ans[++ tot] = siz[x] - 1;
else merge (x, y);
}
while (tot) cout << ans[tot --] << endl;
return 0;
}
2. 题目链接:P4172 [WC2006]水管局长
思路: L C T LCT LCT 离线解决,时间复杂度 O ( n l o g n ) O(nlog\ n) O(nlog n) 。我们发现删边操作很难实现,于是倒着处理所有询问,删边就变成了加边。然后查询所有路径上最大值的最小值,不难发现就是要维护这个图的最小生成树,然后就可以直接查询树路径上的最大值了,可以用 L C T LCT LCT 做到。从而问题转化为 动态维护最小生成树 。难度比上一题较易。
#include <bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define re register
#define endl '\n'
using namespace std;
const int MOD = 998244353;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
const double Pi = acos(-1.0);
const int N = 1e3 + 5, M = 2e5 + 5;
#define ls(x) (ch[x][0])
#define rs(x) (ch[x][1])
int n, m, qur;
map<pair<int, int>, int> id;
struct edge { int u, v, w; } e[M];
struct que { int op, u, v, flag; } q[M];
auto cmp = [](edge a, edge b)->bool{return a.w < b.w;};
int fa[M], ch[M][2];
int mx[M], val[M], ans[M];
bool rev[M], vis[M];
inline bool isroot (int x) { return ls(fa[x]) != x && rs(fa[x]) != x; }
inline void pushup (int x) {
mx[x] = val[x];
if (e[mx[x]].w < e[mx[ls(x)]].w) mx[x] = mx[ls(x)];
if (e[mx[x]].w < e[mx[rs(x)]].w) mx[x] = mx[rs(x)];
}
inline void pushrev (int x) {
swap (ls(x), rs(x));
rev[x] ^= 1;
}
inline void pushdown (int x) {
if (rev[x]) {
if (ls(x)) pushrev (ls(x));
if (rs(x)) pushrev (rs(x));
rev[x] = 0;
}
}
int st[M], top;
inline void rotate (int x) {
int y = fa[x], z = fa[y], k = rs(y) == x;
if (! isroot (y)) ch[z][rs(z) == y] = x;
ch[y][k] = ch[x][k ^ 1], ch[x][k ^ 1] = y;
if (ch[y][k]) fa[ch[y][k]] = y;
fa[x] = z, fa[y] = x;
pushup (y), pushup (x);
}
inline void splay (int x) {
int r = x;
st[top = 1] = r;
while (! isroot (r)) st[++ top] = r = fa[r];
while (top) pushdown (st[top --]);
while (! isroot (x)) {
int y = fa[x], z = fa[y];
if (! isroot (y))
rotate ((rs(y) == x) ^ (rs(z) == y) ? x : y);
rotate (x);
}
pushup (x);
}
inline void access (int x) {
for (int y = 0; x; x = fa[y = x])
splay (x), rs(x) = y, pushup (x);
}
inline void makeroot (int x) { access (x), splay (x), pushrev (x); }
inline void split (int x, int y) { makeroot (x), access (y), splay (y); }
inline void link (int x, int y) { makeroot (x), fa[x] = y; }
inline void cut (int x, int y) { split (x, y), ls(y) = fa[x] = 0; }
inline int findroot (int x) {
access (x), splay (x);
while (ls(x)) pushdown (x), x = ls(x);
return x;
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m >> qur, e[0].w = 0;
for (int i = 1; i <= m; i ++) {
cin >> e[i].u >> e[i].v >> e[i].w;
if (e[i].u > e[i].v) swap (e[i].u, e[i].v);
}
for (int i = 1; i <= n; i ++) val[i] = mx[i] = 0;
for (int i = 1; i <= m; i ++) val[i + n] = mx[i + n] = i;
sort (e + 1, e + 1 + m, cmp);
for (int i = 1; i <= m; i ++) id[make_pair(e[i].u, e[i].v)] = i;
for (int i = 1; i <= qur; i ++) {
cin >> q[i].op >> q[i].u >> q[i].v;
if (q[i].u > q[i].v) swap (q[i].u, q[i].v);
if (q[i].op == 2) {
q[i].flag = id[make_pair(q[i].u, q[i].v)];
vis[q[i].flag] = true;
}
}
for (int i = 1; i <= m; i ++) {
if (vis[i]) continue;
int u = e[i].u, v = e[i].v;
if (findroot (u) == findroot (v)) continue;
link (u, i + n), link (v, i + n);
}
for (int i = qur; i >= 1; i --) {
int u = q[i].u, v = q[i].v;
split (u, v);
if (q[i].op == 1) {
ans[i] = e[mx[v]].w;
}else {
int t = q[i].flag, d = mx[v];
if (e[t].w < e[d].w) {
cut (e[d].u, d + n), cut (e[d].v, d + n);
link (u, t + n), link (v, t + n);
}
}
}
for (int i = 1; i <= qur; i ++)
if (q[i].op == 1) cout << ans[i] << endl;
return 0;
}
3. 题目链接:P2387 [NOI2014] 魔法森林
思路: L C T LCT LCT 解决,时间复杂度 O ( n l o g n ) O(nlog\ n) O(nlog n) 。直接求解比较麻烦。我们考虑按照 a i a_i ai 为关键字将每一条边排序,然后按顺序动态加入每一条边,这样我们每次固定 a i a_i ai ,利用 L C T LCT LCT 维护 b i b_i bi 的最大值的最小值。这里三题都用到了一个把边权转化为点权的技巧,我们将边在 L C T LCT LCT 理解为点,我们插入两条边一个点,从而实现将边插入。现在我们的问题变成了求解出现环的问题,我们考虑贪心,假设我们现在要连边 ( u , v ) (u, v) (u,v) ,但 u u u 和 v v v 之间已经存在一条路径。这样就必须在 u u u 到 v v v 的路径中选一条边断开。由于操作后要求得最小代价,因此要在 u u u 到 v v v 的路径中,选择一条边权最大的边断开后再连入边 ( u , v ) (u, v) (u,v) ,当然这些都是在 u u u 到 v v v 已经连通的情况下,不连通时我们直接连上即可。
#include <bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define re register
#define endl '\n'
using namespace std;
const int MOD = 998244353;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
const double Pi = acos(-1.0);
const int N = 2e5 + 5;
#define ls(x) (ch[x][0])
#define rs(x) (ch[x][1])
int n, m;
struct edge { int x, y, a, b; } e[N];
auto cmp = [](edge u, edge v)->bool{ return u.a < v.a; };
int fa[N], ch[N][2], mx[N], val[N];
bool rev[N];
inline bool isroot (int x) { return ls(fa[x])!=x && rs(fa[x])!=x; }
inline void pushrev (int x) {
swap (ls(x), rs(x));
rev[x] ^= 1;
}
inline void pushdown (int x) {
if (rev[x]) {
if (ls(x)) pushrev (ls(x));
if (rs(x)) pushrev (rs(x));
rev[x] = 0;
}
}
inline void pushup (int x) {
mx[x] = val[x];
if (e[mx[x]].b < e[mx[ls(x)]].b) mx[x] = mx[ls(x)];
if (e[mx[x]].b < e[mx[rs(x)]].b) mx[x] = mx[rs(x)];
}
inline void rotate (int x) {
int y = fa[x], z = fa[y], k = rs(y) == x;
if (! isroot (y)) ch[z][rs(z) == y] = x;
ch[y][k] = ch[x][k ^ 1], ch[x][k ^ 1] = y;
if (ch[y][k]) fa[ch[y][k]] = y;
fa[x] = z, fa[y] = x;
pushup (y), pushup (x);
}
int st[N], top;
inline void splay (int x) {
int r = x;
st[top = 1] = r;
while (! isroot (r)) st[++ top] = r = fa[r];
while (top) pushdown (st[top --]);
while (! isroot (x)) {
int y = fa[x], z = fa[y];
if (! isroot (y))
rotate ((rs(y) == x) ^ (rs(z) == y) ? x : y);
rotate (x);
}
pushup (x);
}
inline void access (int x) {
for (int y = 0; x; x = fa[y = x])
splay (x), rs(x) = y, pushup (x);
}
inline void makeroot (int x) { access (x), splay (x), pushrev (x); }
inline void split (int x, int y) { makeroot (x), access (y), splay (y); }
inline void link (int x, int y) { makeroot (x), fa[x] = y; }
inline void cut (int x, int y) { split (x, y), fa[x] = ls(y) = 0; }
inline int findroot (int x) {
access (x), splay (x);
while (ls(x)) pushdown (x), x = ls(x);
return x;
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= m; i ++)
cin >> e[i].x >> e[i].y >> e[i].a >> e[i].b;
sort (e + 1, e + 1 + m, cmp);
for (int i = 1; i <= n; i ++) mx[i] = val[i] = 0;
for (int i = 1; i <= m; i ++) mx[i + n] = val[i + n] = i;
int ans = inf;
for (int i = 1; i <= m; i ++) {
int u = e[i].x, v = e[i].y;
if (findroot (u) == findroot (v)) {
split (u, v);
int t = mx[v];
if (e[i].b < e[t].b) {
cut (e[t].x, t + n), cut (e[t].y, t + n);
link (u, i + n), link (v, i + n);
}
}else link (u, i + n), link (v, i + n);
split (1, n);
if (findroot (1) == findroot (n))
ans = min (ans, e[i].a + e[mx[n]].b);
}
if (ans == inf) ans = -1;
cout << ans << endl;
return 0;
}