边带权 银河英雄传说 有 $T$ 条指令,每条指令格式为以下两种之一:
$M-i-j$,表示让第 $i$ 号战舰所在列的全部战舰保持原有顺序,接在第 $j$ 号战舰所在列的尾部。
$C-i-j$,表示询问第 $i$ 号战舰与第 $j$ 号战舰当前是否处于同一列中,如果在同一列中,它们之间间隔了多少艘战舰。
数据范围:$N≤30000, T≤500000$
Solution 维护数组 $dp$ 表示 $i$ 到 $root$ 的距离,那么查询的答案便是 $abs(dp[a] - dp[b]) - 1$。
当 $a$ 向 $b$ 连一条边时,有 $fa[a] = b$,此时根结点 $a$ 的深度会增加 $b$ 的集合大小,因此我们需要一个 $sz$ 数组维护集合大小。
同时在进行路径压缩时,对于未更新的结点也要一同更新深度。
Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <bits/stdc++.h> using namespace std ;const int N = 1e6 + 5 ;int dp[N], sz[N], fa[N];int find (int x) { if (x != fa[x]) { int root = find(fa[x]); dp[x] += dp[fa[x]]; fa[x] = root; } return fa[x]; } int main () { int t, a, b; string s; cin >> t; for (int i = 1 ; i <= 30000 ; i++) { dp[i] = 0 ; sz[i] = 1 ; fa[i] = i; } while (t--) { cin >> s >> a >> b; int pa = find(a), pb = find(b); if (s == "M" ) { if (pa != pb) { fa[pa] = pb; dp[pa] = sz[pb]; sz[pb] += sz[pa]; } } else { if (pa != pb) cout << -1 << endl ; else cout << max(abs (dp[a] - dp[b]) - 1 , 0 ) << endl ; } } return 0 ; }
拓展域 食物链 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。
$1-X-Y$,表示 $X$ 和 $Y$ 是同类。
$2-X-Y$,表示 $X$ 吃 $Y$。
判断有多少句假话。$(1≤N≤50000,0≤K≤100000)$
Solution $1-n$ 表示动物 $i$ 的同类,$n+1-2n$ 表示动物 $i$ 的猎物,$2n+1 - 3n$ 表示动物 $i$ 的天敌。
对于操作1: 查询 $x$ 和 $y$ 的天敌域,猎物域是不是在一个集合,是则累加答案,否则进行合并。
对于操作2: 查询 $x$ 和 $y$ 的同类域,猎物域是不是在一个集合,是则累加答案,否则进行合并。
Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <bits/stdc++.h> using namespace std ;int fa[1000005 ];int find (int x) { if (x != fa[x]) fa[x] = find(fa[x]); return fa[x]; } void join (int a, int b) { a = find(a), b = find(b); if (a != b) fa[a] = b; } int main () { int n, k, a, b, c, res = 0 ; cin >> n >> k; for (int i = 1 ; i <= n * 3 ; i++) fa[i] = i; while (k--) { cin >> c >> a >> b; if (a > n || b > n) { res++; continue ; } if (c == 1 ) { if (find(a) == find(b + n) || find(a) == find(b + 2 * n)) { res++; continue ; } else { join(a, b); join(a + n, b + n); join(a + 2 * n, b + 2 * n); } } else { if (a == b || find(a) == find(b) || find(a) == find(b + n)) { res++; continue ; } else { join(a, b + 2 * n); join(a + n, b); join(a + 2 * n, b + n); } } } cout << res << endl ; return 0 ; }
奇偶游戏 给你 $m$ 个询问,每一个询问给出一个区间的左右端点和区间中的 $1$ 的数量的奇偶性,输出不出现矛盾的最大的 $k$ 值,即 $1-k$ 无矛盾,$1- k + 1$ 矛盾。$(N≤10^9,M≤10000)$
Solution1(边带权)
我们可以用 $sum$ 数组表示序列 S 的前缀和,那么会得到以下性质.
$s[l~r]$ 有偶数个 $1$,等价于 $sum[l-1]$ 与 $sum[r]$ 奇偶性相同 $(1+0=1,0+0=0,1是奇数,0是偶数)$ $s[l~r]$ 有奇数个 $1$,等价于 $sum[l-1]$ 与 $sum[r]$ 奇偶性不同 $(1+1=0,0+1=0,1是奇数,0是偶数)$
根据传递性,可以使用边带权,边权 $d[x] = 0$,表示 $x$ 与 $f[x]$ 的奇偶性相同; 为 $1$,表示 $x$ 与 $fa[x]$ 的奇偶性不同,在路径压缩的过程中,对 $x$ 到树根路径的所有边权做异或$(xor)$。
对于每个问题, 设离散化后 $l - 1$ 和 $r$ 的值分别是 $x$ 和 $y$ , 设 $c$ 表示当前问题的回答($c = 0$ 表示偶数个, $c = 1$ 表示奇数个)
若 $x$ 和 $y$ 在一个集合中, 直接判断 $d[x] xor d[y]$ 是否等于 $c$,若不等于,则矛盾,直接输出结果。
若 $x$ 和 $y$ 不在一个集合中,说明无法判断,此时合并两个集合,得到俩个的树根 $p$ 和 $q$, $d[x]$ 与 $d[y]$ 分别表示 $x - p$ 与 $y - q$ 之间所有边权的 “xor” 和,$p - q$ 之间的边权为 $d[p]$, 显然, 路径 $x - y$ 由 $x - p$, $p - q$, $q - y$ 组成,所以 $x$ 与 $y$ 的奇偶性关系 $c = d[x] (xor) d[y] (xor) d[p]$,得到 $d[p] = d[x] (xor) d[y] (xor) c$。
Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <bits/stdc++.h> using namespace std ;const int N = 2e5 + 5 ;int fa[N], d[N];unordered_map <int , int > mp;vector <int > g;struct Node { int a, b, c; } q[N]; int find (int x) { if (x != fa[x]) { int root = find(fa[x]); d[x] ^= d[fa[x]]; fa[x] = root; } return fa[x]; } int main () { int L, n, a, b, pa, pb; string s; cin >> L >> n; for (int i = 1 ; i <= n; i++) { cin >> q[i].a >> q[i].b >> s; if (s[0 ] == 'e' ) q[i].c = 0 ; else q[i].c = 1 ; g.push_back(q[i].a - 1 ); g.push_back(q[i].b); } sort(g.begin(), g.end()); g.erase(unique(g.begin(), g.end()), g.end()); for (int i = 1 ; i <= (int )g.size(); i++) { fa[i] = i; mp[g[i - 1 ]] = i; d[i] = 0 ; } int i = 1 ; for (; i <= n; i++) { a = mp[q[i].a - 1 ], b = mp[q[i].b]; pa = find(a), pb = find(b); if (pa != pb) { fa[pa] = pb; d[pa] ^= d[a] ^ d[b] ^ q[i].c; } else { if (d[a] ^ d[b] != q[i].c) break ; } } cout << i - 1 << endl ; return 0 ; }
Solution2(拓展域) $1 - n$ 表示 $sum[i]$ 为奇,$n + 1 - 2 * n$ 表示 $sum[i]$ 为偶。
当查询区间为奇数时:判断 $a$ 的奇数域是否与 $b$ 的奇数域同在一个集合,是则矛盾,否则合并 $a$ 的奇数域与 $b$ 的偶数域,$a$ 的偶数域与 $b$ 的奇数域。
当查询区间为偶数时:判断 $a$ 的奇数域是否与 $b$ 的偶数域同在一个集合,是则矛盾,否则合并 $a,b$ 的奇数域和偶数域。
Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <bits/stdc++.h> using namespace std ;const int N = 2e5 + 5 ;int fa[N];vector <int > g;unordered_map <int , int > mp;struct Node { int a, b, c; } q[N]; int find (int x) { if (x != fa[x]) fa[x] = find(fa[x]); return fa[x]; } void join (int x, int y) { x = find(x), y = find(y); if (x != y) fa[x] = y; } int main () { int L, m, n, a, b; string s; cin >> L >> m; for (int i = 1 ; i <= m; i++) { cin >> q[i].a >> q[i].b >> s; if (s == "even" ) q[i].c = 0 ; else q[i].c = 1 ; g.push_back(q[i].a - 1 ); g.push_back(q[i].b); } sort(g.begin(), g.end()); g.erase(unique(g.begin(), g.end()), g.end()); n = g.size(); for (int i = 1 ; i <= n; i++) { mp[g[i - 1 ]] = i; fa[i] = i; fa[i + n] = i + n; } int i = 1 ; for (; i <= m; i++) { a = mp[q[i].a - 1 ], b = mp[q[i].b]; if (q[i].c) { if (find(a) == find(b) || find(a + n) == find(b + n)) break ; join(a, b + n); join(a + n, b); } else { if (find(a) == find(b + n) || find(a + n) == find(b)) break ; join(a, b); join(a + n, b + n); } } cout << i - 1 << endl ; return 0 ; }
反向并查集 星球大战 求每次拆边后的连通块个数。$(m \in [1, 2e5], n \in [1, 2*m])$
Solution 拆边很难维护集合数量,考虑离线后反过来建图。这样便相当于每次增加增加一条边,查询两个点是否为同一个集合即可知道集合数量是否减少。
Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #include <bits/stdc++.h> using namespace std ;const int N = 5e5 + 5 ;int n, m, k, pre[N], b[N], vis[N];vector <int > g[N];int find (int x) { int i = x, j = x; while (i != pre[i]) i = pre[i]; while (i != j) { int temp = pre[j]; pre[j] = i; j = temp; } return i; } bool join (int x, int y) { int fx = find(x), fy = find(y); if (fx != fy) { pre[fx] = fy; return true ; } return false ; } int main () { cin >> n >> m; for (int i = 0 ; i < n; i++) pre[i] = i; for (int i = 1 ; i <= m; i++) { int x, y; cin >> x >> y; g[x].push_back(y); g[y].push_back(x); } cin >> k; for (int i = 1 ; i <= k; i++) { cin >> b[i]; vis[b[i]] = 1 ; } int res = n - k; for (int i = 0 ; i < n; i++) { if (vis[i]) continue ; for (int j = 0 ; j < g[i].size(); j++) { int x = g[i][j]; if (!vis[x]) { if (join(i, x)) res--; } } } b[k + 1 ] = res; for (int i = k; i >= 1 ; i--) { res++; vis[b[i]] = 0 ; for (int j = 0 ; j < g[b[i]].size(); j++) { if (!vis[g[b[i]][j]] && join(b[i], g[b[i]][j])) res--; } b[i] = res; } for (int i = 1 ; i <= k + 1 ; i++) cout << b[i] << endl ; return 0 ; }
Total Eclipse 给你 $n$ 个节点 $m$ 条边的图,每个点有一个权值,你现在要做的操作是选择一个连通图,并将其中的每一个点的权值都减一,问你最少需要多少次才能将所有的点都变为0。$(1≤n≤100000, 1≤m≤200000)$
Solution 贪心地想,每次必然是选择权值最小的点,然后联通的边都减少该权值,但这样很难维护,因此可以考虑反向。
每次选择权值最大的点,然后这个点需要减少到和次小的点一样的权值,即减少的权值为和次小点权值之差。由于一次可以减少一个联通块,因此我们只需要乘以当前点联通块个数即可。
Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 #include <bits/stdc++.h> using namespace std ;const int N = 2e5 + 5 ;int t, n, m, pre[N], vis[N];vector <int > g[N];struct Node { long long w; int id; } q[N]; bool cmp (Node x, Node y) { return x.w > y.w; }int find (int x) { int i = x, j = x; while (i != pre[i]) i = pre[i]; while (j != i) { int temp = pre[j]; pre[j] = i; j = temp; } return i; } bool join (int x, int y) { int fx = find(x), fy = find(y); if (fx != fy) { pre[fx] = fy; return true ; } return false ; } int main () { scanf ("%d" , &t); while (t--) { scanf ("%d%d" , &n, &m); memset (vis, 0 , sizeof (vis)); for (int i = 1 ; i <= n; i++) { scanf ("%lld" , &q[i].w); q[i].id = i; pre[i] = i; g[i].clear(); } for (int i = 1 ; i <= m; i++) { int u, v; scanf ("%d%d" , &u, &v); g[u].push_back(v); g[v].push_back(u); } long long cnt = 0 , res = 0 ; sort(q + 1 , q + n + 1 , cmp); q[n + 1 ].w = 0 ; for (int i = 1 ; i <= n; i++) { cnt++; vis[q[i].id] = 1 ; for (auto x : g[q[i].id]) { if (vis[x] && join(x, q[i].id)) cnt--; } res += cnt * (q[i].w - q[i + 1 ].w); } printf ("%lld\n" , res); } return 0 ; }