洛谷 P11967 [GESP202503 八级] 割裂-普及+/提高
题目描述
小杨有一棵包含 $ n $ 个节点的树,其中节点的编号从 1 到 $ n $。
小杨设置了一个好点对 { ⟨ u 1 , v 1 ⟩ , ⟨ u 2 , v 2 ⟩ , … , ⟨ u a , v a ⟩ } \{\langle u_1, v_1 \rangle, \langle u_2, v_2 \rangle, \dots, \langle u_a, v_a \rangle\} {⟨u1,v1⟩,⟨u2,v2⟩,…,⟨ua,va⟩} 和一个坏点对 ⟨ b u , b v ⟩ \langle b_u, b_v \rangle ⟨bu,bv⟩。一个节点能被删除,当且仅当:
- 删除该节点后对于所有的 $ 1 \leq i \leq a $,好点对 $ u_i $ 和 $ v_i $ 仍然连通;
- 删除该节点后坏点对 $ b_u $ 和 $ b_v $ 不连通。
如果点对中的任意一个节点被删除,其视为不连通。
小杨想知道,还有多少个节点能被删除。
输入格式
第一行包含两个非负整数 $ n $, $ a $,含义如下题面所示。
接下来 n − 1 n - 1 n−1 行,每行包含两个正整数 $ x_i, y_i $,代表存在一条连接节点 $ x_i $ 和 $ y_i $ 的边;
之后 $ a $ 行,每行包含两个正整数 $ u_i, v_i $,代表一个好点对 $ \langle u_i, v_i \rangle $;
最后一行包含两个正整数 $ b_u, b_v $,代表坏点对 $ \langle b_u, b_v \rangle $。
输出格式
输出一个非负整数,代表删除的节点个数。
输入输出样例 #1
输入 #1
6 2
1 3
1 5
3 6
3 2
5 4
5 4
5 3
2 6
输出 #1
2
说明/提示
子任务编号 | 分值 | $ n $ | $ a $ |
---|---|---|---|
1 | 20 20 20 | = 10 =10 =10 | = 0 =0 =0 |
2 | 20 20 20 | $ \leq 100 $ | $ \leq 100 $ |
3 | 60 60 60 | $ \leq 10^6 $ | $ \leq 10^5 $ |
对于全部数据,保证有 $ 1 \leq n \leq 10^6 $, $ 0 \leq a \leq 10^5 $, $ u_i \neq v_i $, $ b_u \neq b_v $。
solution
如果 u ,v 的路径上的一点被删除,则 u, v 不联通,所以被删除的点应该满足两个条件
- 在坏点对点路径上
- 不在任何好点对的路径上
所以可以遍历坏点对路径上的每一点,如果它不在任何好点对上则满足条件。可以结合最近公共祖先完成判断。
代码
#include <iostream>
#include "bit"
#include "vector"
#include "unordered_set"
#include "unordered_map"
#include "set"
#include "queue"
#include "algorithm"
#include "bitset"
#include "cstring"
#include "cmath"using namespace std;const int N = 1e6 + 1, M = 1e5 + 1;
int n, a, U[M], V[M], s, t, f[N][21], d[N];
vector<int> e[N];void dfs(int u, int p) {d[u] = d[p] + 1;f[u][0] = p;for (int i = 1; i <= 20; i++) f[u][i] = f[f[u][i - 1]][i - 1];for (int v: e[u]) {if (v != p) {dfs(v, u);}}
}int lca(int x, int y) {if (x == y) return x;if (d[x] < d[y]) swap(x, y);for (int i = 20; d[x] > d[y]; i--) {if (d[f[x][i]] >= d[y])x = f[x][i];}if (x == y) return x;for (int i = 20; i >= 0; i--) {if (f[x][i] != f[y][i])x = f[x][i], y = f[y][i];}return f[x][0];
}int main() {scanf("%d %d", &n, &a);for (int i = 1; i < n; i++) {int x, y;scanf("%d %d", &x, &y);e[x].push_back(y);e[y].push_back(x);}for (int i = 1; i <= a; i++) scanf("%d %d", &U[i], &V[i]);scanf("%d %d", &s, &t);dfs(1, 0);int p = lca(s, t);int cnt = 0;for (; s != p; s = f[s][0]) {int tt = 1;for(int i = 1; i <= a; i++){int P = lca(U[i], V[i]);if(lca(P, s) == P && (lca(s, U[i]) == s || lca(s, V[i]) == s)){tt = 0;break;}}cnt += tt;}for (; ; t = f[t][0]) {int tt = 1;for(int i = 1; i <= a; i++){int P = lca(U[i], V[i]);if(lca(P, t) == P && (lca(t, U[i]) == t || lca(t, V[i]) == t)){tt = 0;break;}}cnt += tt;if(t == p) break;}cout << cnt;
}
- 用 dfn 序求最近公共祖先,可以将对数级别降低到常数级别。
#include <iostream>
#include "bit"
#include "vector"
#include "unordered_set"
#include "unordered_map"
#include "set"
#include "queue"
#include "algorithm"
#include "bitset"
#include "cstring"
#include "cmath"using namespace std;const int N = 1e6 + 1, M = 1e5 + 1;
int n, a, U[M], V[M], s, t, f[N][21], id, dfn[N];
vector<int> e[N];void dfs(int u, int p) {f[dfn[u] = ++id][0] = p;for (int v: e[u]) {if (v != p) {dfs(v, u);}}
}inline int my_min(int x, int y){return dfn[x] < dfn[y] ? x : y;
}int lca(int x, int y) {if (x == y) return x;x = dfn[x], y = dfn[y];if (x > y) swap(x, y);int d = (int)log2(y - x);x++;// 求 x y 之间 dfn 最小的那个数return my_min(f[x][d], f[y - (1 << d) + 1][d]);
}int main() {scanf("%d %d", &n, &a);for (int i = 1; i < n; i++) {int x, y;scanf("%d %d", &x, &y);e[x].push_back(y);e[y].push_back(x);}for (int i = 1; i <= a; i++) scanf("%d %d", &U[i], &V[i]);scanf("%d %d", &s, &t);dfs(1, 0);for (int j = 1; j < log2(n) + 1; j++) {for (int i = 1; i + (1 << (j - 1)) <= n; i++) {f[i][j] = my_min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);}}int p = lca(s, t);int cnt = 0;for (; s != p; s = f[dfn[s]][0]) {int tt = 1;for (int i = 1; i <= a; i++) {int P = lca(U[i], V[i]);if (lca(P, s) == P && (lca(s, U[i]) == s || lca(s, V[i]) == s)) {tt = 0;break;}}cnt += tt;}for (;; t = f[dfn[t]][0]) {int tt = 1;for (int i = 1; i <= a; i++) {int P = lca(U[i], V[i]);if (lca(P, t) == P && (lca(t, U[i]) == t || lca(t, V[i]) == t)) {tt = 0;break;}}cnt += tt;if (t == p) break;}cout << cnt;}
- 由于输入数据量达到百万级别,改用 getchar 快速读入可以节约时间
#include <iostream>
#include "bit"
#include "vector"
#include "unordered_set"
#include "unordered_map"
#include "set"
#include "queue"
#include "algorithm"
#include "bitset"
#include "cstring"
#include "cmath"using namespace std;const int N = 1e6 + 1, M = 1e5 + 1;
int n, a, U[M], V[M], s, t, f[N][21], id, dfn[N];
vector<int> e[N];void dfs(int u, int p) {f[dfn[u] = ++id][0] = p;for (int v: e[u]) {if (v != p) {dfs(v, u);}}
}inline int read() {int s = 0, w = 1;int ch = getchar();while (ch < '0' || ch > '9') { if (ch == '-') w = -1; ch = getchar(); }while (ch >= '0' && ch <= '9') { s = s * 10 + ch - '0'; ch = getchar();}return s * w;
}inline int my_min(int x, int y){return dfn[x] < dfn[y] ? x : y;
}int lca(int x, int y) {if (x == y) return x;x = dfn[x], y = dfn[y];if (x > y) swap(x, y);int d = (int)log2(y - x);x++;// 求 x y 之间 dfn 最小的那个数return my_min(f[x][d], f[y - (1 << d) + 1][d]);
}int main() {n = read(), a = read();for (int i = 1; i < n; i++) {int x, y;x = read(), y = read();e[x].push_back(y);e[y].push_back(x);}for (int i = 1; i <= a; i++) U[i] = read(), V[i] = read();//scanf("%d %d", &U[i], &V[i]);s = read(), t = read();dfs(1, 0);for (int j = 1; j < log2(n) + 1; j++) {for (int i = 1; i + (1 << (j - 1)) <= n; i++) {f[i][j] = my_min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);}}int p = lca(s, t);int cnt = 0;for (; s != p; s = f[dfn[s]][0]) {int tt = 1;for (int i = 1; i <= a; i++) {int P = lca(U[i], V[i]);if (lca(P, s) == P && (lca(s, U[i]) == s || lca(s, V[i]) == s)) {tt = 0;break;}}cnt += tt;}for (;; t = f[dfn[t]][0]) {int tt = 1;for (int i = 1; i <= a; i++) {int P = lca(U[i], V[i]);if (lca(P, t) == P && (lca(t, U[i]) == t || lca(t, V[i]) == t)) {tt = 0;break;}}cnt += tt;if (t == p) break;}cout << cnt;
}
结果
用树上倍增求最近公共祖先的时间
用 dfn 序列求最近公共祖先的时间
改用快速读入时通过的时间
小结
这高低复杂度用树上差分好像复杂度更优一点