$Kruskal$ 重构树
参考 \(Kruskal\) 构建最小生成树的过程,把边按权升序排序,用并查集判断连通性,这里并查集不能做按秩合并。
考虑一条边 \([u,v,w]\),如果 \(u\) 和 \(v\) 属于同一连通分量,跳过;否则新建一个虚拟点,虚拟点的点权为 \(w\),把 \(p[u]\) 和 \(p[v]\) 指向这个虚拟点,同时添加从虚拟点到 \(u\) 和 \(v\) 的有向边。可以发现,\(Kruskal\) 重构树中的叶子节点为原图的节点。
1.https://www.luogu.com.cn/problem/P2245
题意概述:给定一张带权无向图,有若干询问,每个询问求出 从 \(u\) 到 \(v\) 路径上边权最大值的可能最小值。
构建 \(Kruskal\) 重构树,相当于询问求 \(u\) 和 \(v\) 的最近公共祖先的权值,在构建完树后 \(dfs\) 一次维护深度信息和倍增表信息即可,之后就是标准的求 \(LCA\)。这样可以做到在线查询,每次查询 \(\mathcal{O}(n \log n)\)。
//author:kzssCCC
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solve(){
int n,m;
cin >> n >> m;
vector<array<int,3>> edges(m);
for (int i=0;i<m;i++){
int u,v,w;
cin >> u >> v >> w;
edges[i] = {u,v,w};
}
sort(edges.begin(),edges.end(),[](auto& p1,auto& p2){
return p1[2] < p2[2];
});
vector<int> p(n+1);
iota(p.begin(),p.end(),0);
vector<int> val(n+1,0);
vector<vector<int>> adj(n+1);
int tot = 1;
auto find = [&](int u){
int v = u;
while (v!=p[v]){
v = p[v];
}
while (u!=v){
int next = p[u];
p[u] = v;
u = next;
}
return v;
};
for (auto& [u,v,w]:edges){
int a = find(u);
int b = find(v);
if (a==b) continue;
p.push_back(n+tot);
val.push_back(w);
adj.push_back({a,b});
p[a] = n+tot;
p[b] = n+tot;
tot++;
}
n = n+tot-1;
vector<int> lg(n+1,0);
for (int i=2;i<=n;i++){
lg[i] = lg[i>>1]+1;
}
int K = lg[n];
vector<vector<int>> dp(n+1,vector<int>(K+1,-1));
vector<int> depth(n+1,0);
function<void(int)> dfs = [&](int u){
for (auto& v:adj[u]){
depth[v] = depth[u]+1;
dp[v][0] = u;
dfs(v);
}
};
for (int i=1;i<=n;i++){
if (p[i]==i){
dfs(i);
}
}
for (int k=1;k<=K;k++){
for (int i=1;i<=n;i++){
if (dp[i][k-1]==-1) continue;
dp[i][k] = dp[dp[i][k-1]][k-1];
}
}
auto getlca = [&](int a,int b){
if (depth[a]<depth[b]){
swap(a,b);
}
int diff = depth[a]-depth[b];
for (int k=K;k>=0;k--){
if (diff>>k&1){
a = dp[a][k];
}
}
if (a==b) return a;
for (int k=K;k>=0;k--){
if (dp[a][k]!=dp[b][k]){
a = dp[a][k];
b = dp[b][k];
}
}
return dp[a][0];
};
int q;
cin >> q;
while (q--){
int a,b;
cin >> a >> b;
if (find(a)!=find(b)){
cout << "impossible" << '\n';
}
else{
cout << val[getlca(a,b)] << '\n';
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}
2.https://www.luogu.com.cn/problem/P9638
在建树的时候要标记改边对应的点的编号,统计 \(size\) 信息即可,注意所有操作3会在操作1统一生效,由于题目保证了修改操作所有边权的相对大小不发生变化,这题才可以用 \(Kruskal\) 重构树。
//author:kzssCCC
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int INF = 1e9;
void solve(){
int n,m,q;
cin >> n >> m >> q;
vector<array<int,3>> edges(m);
for (int i=0;i<m;i++){
int u,v,w;
cin >> u >> v >> w;
edges[i] = {w,u,v};
}
vector<int> ord(m);
iota(ord.begin(),ord.end(),0);
sort(ord.begin(),ord.end(),[&](int i,int j){
return edges[i][0] > edges[j][0];
});
vector<int> ref(m,0);
vector<int> val(n+1,0);
vector<int> p(n+1);
iota(p.begin(),p.end(),0);
int tot = 1;
vector<vector<int>> adj(n+1);
auto find = [&](int u){
int v = u;
while (p[v]!=v){
v = p[v];
}
while (u!=v){
int next = p[u];
p[u] = v;
u = next;
}
return v;
};
for (int i=0;i<m;i++){
auto& [w,u,v] = edges[ord[i]];
int a = find(u);
int b = find(v);
if (a==b) continue;
val.push_back(w);
p.push_back(n+tot);
ref[ord[i]] = n+tot;
adj.push_back({a,b});
p[a] = n+tot;
p[b] = n+tot;
tot++;
}
n += tot-1;
vector<int> sz(n+1,0);
vector<int> lg(n+1,0);
for (int i=2;i<=n;i++){
lg[i] = lg[i>>1]+1;
}
int K = lg[n];
vector<vector<int>> dp(n+1,vector<int>(K+1,-1));
function<void(int)> dfs = [&](int u){
if (u>=1 && u<=n-tot+1){
sz[u] = 1;
}
for (auto& v:adj[u]){
dp[v][0] = u;
dfs(v);
sz[u] += sz[v];
}
};
for (int i=1;i<=n;i++){
if (p[i]==i){
dfs(i);
}
}
for (int k=1;k<=K;k++){
for (int i=1;i<=n;i++){
if (dp[i][k-1]==-1) continue;
dp[i][k] = dp[dp[i][k-1]][k-1];
}
}
int lim = -INF;
vector<pair<int,int>> wait;
while (q--){
int op;
cin >> op;
if (op==1){
int x;
cin >> x;
lim = x;
for (auto& [u,w]:wait){
val[u] = w;
}
wait.clear();
}
else if (op==2){
int x;
cin >> x;
for (int k=K;k>=0;k--){
if (dp[x][k]!=-1 && val[dp[x][k]]>=lim){
x = dp[x][k];
}
}
cout << sz[x] << '\n';
}
else{
int x,y;
cin >> x >> y;
x--;
if (ref[x]!=0){
wait.emplace_back(ref[x],y);
}
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}
3.https://atcoder.jp/contests/agc002/tasks/agc002_b
题意概述:给定一张连通无向图,若干查询给定 \(x\)、\(y\)、\(z\),两个人分别从 \(x\) 和 \(y\),一共需要走过 \(z\) 个不同的节点,求经过边权最大值的最小可能值,边权为边的编号。
建树,维护深度和倍增表信息。对于每个询问,可以二分最大的边权,只要 \(x\) 和 \(y\) 在重构树向上跳跃经过原图的不同节点数量超过 \(z\) 就判断为 \(true\),找到最小的最大边权即可。
具体实现上,先对 \(x\) 和 \(y\) 求 \(LCA\),记为 \(g\),如果 \(g\) 的点权严格大于 \(mid\),说明到不了这个点,那么从 \(x\) 和 \(y\) 各自向上跳跃即可,否则直接从 \(g\) 开始向上跳跃,这样就能保证不会统计到重复的点。
//author:kzssCCC
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solve(){
int n,m;
cin >> n >> m;
vector<pair<int,int>> edges(m+1);
for (int i=1;i<=m;i++){
int u,v;
cin >> u >> v;
edges[i] = {u,v};
}
vector<int> p(n+1);
iota(p.begin(),p.end(),0);
vector<int> val(n+1,0);
vector<vector<int>> adj(n+1);
int tot = 1;
auto find = [&](int u){
int v = u;
while (p[v]!=v){
v = p[v];
}
while (u!=v){
int next = p[u];
p[u] = v;
u = next;
}
return v;
};
for (int i=1;i<=m;i++){
auto& [u,v] = edges[i];
int a = find(u);
int b = find(v);
if (a==b) continue;
val.push_back(i);
p.push_back(n+tot);
adj.push_back({a,b});
p[a] = n+tot;
p[b] = n+tot;
tot++;
}
n += tot-1;
vector<int> sz(n+1,0);
vector<int> lg(n+1,0);
for (int i=2;i<=n;i++){
lg[i] = lg[i>>1]+1;
}
int K = lg[n];
vector<vector<int>> dp(n+1,vector<int>(K+1,-1));
vector<int> depth(n+1,0);
function<void(int)> dfs = [&](int u){
if (adj[u].empty()){
sz[u] = 1;
return;
}
for (auto& v:adj[u]){
dp[v][0] = u;
depth[v] = depth[u]+1;
dfs(v);
sz[u] += sz[v];
}
};
for (int i=1;i<=n;i++){
if (p[i]==i){
dfs(i);
}
}
for (int k=1;k<=K;k++){
for (int i=1;i<=n;i++){
if (dp[i][k-1]==-1) continue;
dp[i][k] = dp[dp[i][k-1]][k-1];
}
}
auto getlca = [&](int a,int b){
if (depth[a]<depth[b]){
swap(a,b);
}
int diff = depth[a]-depth[b];
for (int k=K;k>=0;k--){
if (diff>>k&1){
a = dp[a][k];
}
}
if (a==b) return a;
for (int k=K;k>=0;k--){
if (dp[a][k]!=dp[b][k]){
a = dp[a][k];
b = dp[b][k];
}
}
return dp[a][0];
};
int q;
cin >> q;
while (q--){
int x,y,z;
cin >> x >> y >> z;
auto check = [&](int mid){
auto cal = [&](int u){
for (int k=K;k>=0;k--){
if (dp[u][k]!=-1 && val[dp[u][k]]<=mid){
u = dp[u][k];
}
}
return sz[u];
};
int r = getlca(x,y);
if (val[r]<=mid){
for (int k=K;k>=0;k--){
if (dp[r][k]!=-1 && val[dp[r][k]]<=mid){
r = dp[r][k];
}
}
return sz[r]>=z;
}
else{
return cal(x)+cal(y)>=z;
}
};
int l=1,r=m;
while (l<=r){
int mid = l+r >> 1;
if (check(mid)){
r = mid-1;
}
else{
l = mid+1;
}
}
cout << l << '\n';
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}

浙公网安备 33010602011771号