Kruskal重构树

定义:将一个 \(n\) 个点的图重构为一个 \(2n-1\) 个点的图,使得这张图有特殊性质可以利用。
在 Kruskal 算法中我们找到两个尚未在一个集合中的点,并将其合并,将其连边作为最小生成树的一条边。
在 Kruskal 重构树算法中,我们首先新建 \(n\) 个集合,每个集合恰有一个节点,点权为 \(0\)

每一次加边会合并两个集合,我们可以新建一个点,点权为加入边的边权,同时将两个集合的根节点分别设为新建点的左儿子和右儿子。然后我们将两个集合和新建点合并成一个集合。将新建点设为根。

不难发现,在进行 \(n-1\) 轮之后我们得到了一棵恰有 \(n\) 个叶子的二叉树,同时每个非叶子节点恰好有两个儿子。这棵树就叫 Kruskal 重构树。

例如下图:
image

它的 Kruskal 重构树就是下图:

image
时间复杂度 \(O(n \log n)\)

性质:
原图中两个点之间的所有简单路径上最大边权的最小值 = 最小生成树上两个点之间的简单路径上的最大值 = Kruskal 重构树上两点之间的 LCA 的权值。
例题:

CF1706E

题意:给定一个 \(n\)\(m\) 边无向连通图,有 \(q\) 次询问,每次给定区间 \([l,r]\),求区间内所有点之间简单路径最大边权的最小值。

分析:转化为 Kruskal 重构树,那么每次就是求区间里每个点的 LCA。
但是区间 LCA 暴力做还是复杂度不够。我们考虑区间 LCA 就是区间上 \(dfn\) 最大和最小的两个点的 LCA,那么我们再用俩 ST 表或者一棵线段树记录区间 \(dfn\) 的最大最小值即可。

#include<bits/stdc++.h>
using namespace std;
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 1e9;
int N = 200000;
int n,m,q;
vector<int> g[200010];
int lg2[200010];
vector<int> dep, dfn;
vector<int> stmx[200010], stmn[200010], anc[200010];
int cha[200010];
struct rec{
	int x,y,z;
}edge[500010];
bool operator<(rec a,rec b){
	return a.z<b.z;
}
int qz[200010];
int fa[200010]; 
int get(int x){
	if(fa[x]==x)return x;
	else return fa[x] = get(fa[x]);
}
int kruskal() {
    int cnt = n;
    f(i,1,2*n)fa[i]=i;
    f(i, 1, m) {
        int x = get(edge[i].x);
		int y = get(edge[i].y);
		if(x == y)continue;
        else {
            int z = ++cnt;
            g[x].push_back(z); g[y].push_back(z);
            g[z].push_back(x); g[z].push_back(y);
            fa[x]=z;fa[y]=z;
            qz[z] = edge[i].z;
        }
    }
    return cnt;
}
int ccnt;
void dfs(int now, int fa) {
    dfn[now] = ++ccnt;
    dep[now] = dep[fa] + 1; anc[now][0] = fa;
    f(i, 1, lg2[dep[now]] - 1) {
        anc[now][i] = anc[anc[now][i - 1]][i - 1];
    }
    f(i, 0, (int)g[now].size() -1 ) {
        if(g[now][i] != fa) dfs(g[now][i], now);
    }
}
int lca(int qx, int qy) {
    if(dep[qx] < dep[qy]) swap(qx, qy);
    while(dep[qx] > dep[qy]) {
        qx = anc[qx][lg2[dep[qx]-dep[qy]] - 1];
    }
    if(qx == qy) return qx;
    for(int k = lg2[dep[qx]] - 1; k >= 0; k--) {
        if(anc[qx][k] != anc[qy][k]) {
            qx = anc[qx][k]; qy = anc[qy][k];
        }
    }
    return anc[qx][0];
}
 
void STmx_prework() {
    f(i, 1, n) stmx[i][0] = dfn[i];
    int mx = log(n) / log(2);
    f(j, 1, mx) {
    	int mxi = n - (1 << j) + 1;
    	f(i, 1, mxi) {
    		stmx[i][j] = max(stmx[i][j - 1], stmx[i + (1 << (j - 1))][j - 1]);
		}
	}
}
void STmn_prework() {
    f(i, 1, n) stmn[i][0] = dfn[i];
    int mx = log(n) / log(2);
    f(j, 1, mx) {
    	int mxi = n - (1 << j) + 1;
    	f(i, 1, mxi) {
    		stmn[i][j] = min(stmn[i][j - 1], stmn[i + (1 << (j - 1))][j - 1]);
		}
	}
}
int querymx(int l, int r) {
    int mx = log(r - l + 1) / log(2);
    int ans;
    ans = max(stmx[l][mx], stmx[r - (1 << mx) + 1][mx]);
    return ans;  
}
int querymn(int l, int r) {
    int mx = log(r - l + 1) / log(2);
    int ans;
    ans = min(stmn[l][mx], stmn[r - (1 << mx) + 1][mx]);
    return ans;  
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    f(i, 1, N) lg2[i] = lg2[i - 1] + (1 << lg2[i - 1] == i);
    int t; cin >> t;
    while(t--) {
        
        cin>>n>>m>>q;
        f(i, 1, m) {
            int u, v; cin >> u >> v; 
            edge[i].x = u, edge[i].y = v, edge[i].z = i;
        }
        sort(edge+1,edge+m+1);
        f(i,1,2*n)g[i].clear();
        int root = kruskal();
        n=2*n;
        cl(dep, n+10);cl(dfn,n+10);
        f(i,0,n+9){
            cl(anc[i],30);cl(stmx[i],30);cl(stmn[i],30);
        }
        
        dfs(root, 0);
        STmx_prework(); STmn_prework();
        f(i,1,n) cha[dfn[i]] = i;
        f(i, 1, q) {
            int l, r; cin >> l >> r; if(l == r) {cout << 0 << " "; continue;}
            int mx = querymx(l, r), mn = querymn(l, r); 
            int lcaa = lca(cha[mx], cha[mn]);
            cout << qz[lcaa] <<" ";
        }
        cout << endl;
    }
    return 0;
}

也就是说,到点 \(x\) 的简单路径上最大边权的最小值 $ \le val$ 的所有点 \(y\) 均在 Kruskal 重构树上的某一棵子树内,且恰好为该子树的所有叶子节点。

我们在 Kruskal 重构树上找到 \(x\) 到根的路径上权值 $ \le val$ 的最浅的节点。显然这就是所有满足条件的节点所在的子树的根节点。

如果需要求原图中两个点之间的所有简单路径上最小边权的最大值,则在跑 Kruskal 的过程中按边权大到小的顺序加边。

posted @ 2022-07-19 10:33  OIer某罗  阅读(406)  评论(0编辑  收藏  举报