克鲁斯卡尔重构树

一类以并查集在建树过程中维护各种信息的值——克鲁斯卡尔重构树前身

第一次见到是在zzu的校赛中,印象深刻

H.Sum of Maximum Weights

题意:给定一棵树,求树上任意两点间最短路径中的最大边权的sum

官方Solution:

  • 我们先将边按权值排序,这样每次处理的都是当前的最大权值

  • 处理每一条边的时候,由于树,这条边的起点和终点一定连通,所以我们设置两个组 \(U\)\(V\)
    那么 \(U\) 组中任意成员到 \(V\) 组中任意成员的最大路径边权必为当前边的权值,设为 \(e\),故可以写出 \(ans\ += siz[u] × siz[v]×e\)
    然后将二组合并,之后对每条边考虑上述情况后即可得出答案

我的理解:如果了解kruskal重构树,这个就是板子题。

```cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e6 + 5;
const int M = N * 2;
int a[N], b[N];
int f[N];
int siz[N];
int Get(int u) {
    return u == f[u] ? u : f[u] = Get(f[u]);
}
struct edge {
    int u, v, e;
} e[N];
bool cmp(edge x, edge y) {
    return x.e < y.e;
}
signed main() {
    int n;
    cin >> n;
    for (int i = 1; i < n; i++) {
        scanf("%lld %lld %lld", &e[i].u, &e[i].v, &e[i].e);
    }
    for (int i = 1; i <= n; i++) {
        f[i] = i;
        siz[i] = 1;
    }
    sort(e + 1, e + n, cmp);
    int ans = 0;
    for (int i = 1; i < n; i++) {
        edge t = e[i];
        int u = t.u, v = t.v, e = t.e;
        u = Get(u), v = Get(v);
        if (siz[u] < siz[v]) {
            swap(u, v);
        }
        ans += siz[u] * siz[v] * e;
        f[u] = v;
        siz[v] += siz[u];
    }
    cout << ans << endl;
} 
```

牛客小白月赛25C

https://ac.nowcoder.com/acm/contest/5600/C

题意:树上有黑白点,可以将一个黑点改成白点,求修改后最大白色连通块。

Solution:首先考虑对于白色连通块用并查集维护连通性和size。暴力枚举黑色节点,累加他相邻的白色连通块大小,取max就是答案

debug:字符串下标和数组下标混用。特判全是白色节点的情况

int n, m;
int a[N];
int col[N];

struct DSU {
  vector<int> f, siz;
    
    DSU() {}
    DSU(int n) {
        init(n);
    }
    
    void init(int n) {
        f.resize(n);
        std::iota(f.begin(), f.end(), 0);
        siz.assign(n, 1);
    }
    
    int find(int x) {
        while (x != f[x]) {
            x = f[x] = f[f[x]];
        }
        return x;
    }
    bool same(int x, int y) {
        return find(x) == find(y);
    }
    
    bool merge(int x, int y) {
        x = find(x);
        y = find(y);
        if (x == y) {
            return false;
        }
        siz[x] += siz[y];
        f[y] = x;
        return true;
    }
    
    int size(int x) {
        return siz[find(x)];
    }
};
vector<int>e[N];
void solve(){
	cin>>n;
	string s;cin>>s;
	for(int i=0;i<s.size();i++){
		if(s[i]=='W')col[i+1]=1;
		else col[i+1]=0;
	}
		DSU dsu(n+1);
	for(int i=1;i<=n-1;i++){
		int u,v;cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
		if(col[u]&&col[v])dsu.merge(u,v);
	}
	//DSU dsu(n+1);
	// for(int i=1;i<=n;i++){
		// if(col[i]==0)continue;
		// for(auto v:e[i]){
			// if(col[v])dsu.merge(i,v);
		// }
	// }
	int ans=0;
	for(int i=1;i<=n;i++){
		int sum=0;
		if(col[i]==0){
			for(auto v:e[i]){
				if(col[v]==1)sum+=dsu.siz[dsu.find(v)];
			}
			ans=max(ans,sum+1);
		}
		
	}
	if(ans==0)ans=n;
	cout<<ans<<endl;
}

2020牛客寒假算法基础集训营1 F

有一天,maki拿到了一颗树。所谓树,即没有自环、重边和回路的无向连通图。 这个树有 $n\ $ 个顶点, $n-1\ $ 条边。每个顶点被染成了白色或者黑色。 maki想知道,取两个不同的点,它们的简单路径上有且仅有一个黑色点的取法有多少? 注: ①树上两点简单路径指连接两点的最短路。 ② $\ $ 和 \(<p,q>\)\(<q,p>\)的取法视为同一种。

Solution:经过一个黑点的路径有两种:两个端点都是白点;其中一个端点是黑点。 因此我们可以先预处理,将每个白点连通块上的白点个数统计出来。这样我们就可以得知每个黑点所连接的白点的权值(即连通块白点数)。 设某黑点连接了 \(k\) 个白点,第 \(i\) 个白点的权值为 \(f(i)\)

  • 这样第一种路径的数量为\(\mathop{ \sum }\limits_{{i=1}}^{{k}}{{\mathop{ \sum }\limits_{{j=i+1}}^{{k}}{f \left( i \left) *f \left( j \right) \right. \right. }}}\)(注意可以用前缀和处理达成 \(O(k)\) 计算)
  • 第二种路径的数量为\({\mathop{ \sum }\limits_{{i=1}}^{{k}}{f \left( i \right) }}\)

先给出一道克鲁斯卡尔重构树的板子题[P1967 NOIP2013 提高组] 货车运输 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题意:给出一张普通无向图,多次查询。每次查询的具体内容是,给出两个点,求连通两个点的所有路径中,每条路径都有最小边权值,求在这些最小边权值中最大的那个值

Solution:首先考虑路径问题,涉及多条路径不能直接做。我们考虑如果找路径,我们先跑最大生成树,然后发现最大生成树上路径最小值就是答案。

问题转化为如何快速求出树上任意两点的路径最小值,一种比较重工业的做法是:树链剖分加线段树维护区间最小值。这里给出克鲁斯卡尔重构树的解法。

在克鲁斯卡尔生成树的过程中,每当两个点要很合并的时候,我们都开一个新点,注意并查集合并的过程中,我们开一个新点,把两个要合并的点都作为子节点,其中需要刻意维护父节点关系,让新点做父节点,新点的权值是本次合并初始两点的边的权值权重,保证生成的克鲁斯卡尔重构树是正确的

经过上面的建树,每次查询只需要找到这个两个点的lca,然后lca的权值就是答案。

int n, m;
int a[N];
vector<int> e[N];
int fa[N],son[N],dep[N],siz[N];
int top[N];
void dfs1(int u,int f){ //搞fa,dep,son
  fa[u]=f;siz[u]=1;dep[u]=dep[f]+1;
  for(int v:e[u]){
    if(v==f) continue;
    dfs1(v,u);
    siz[u]+=siz[v];
    if(siz[son[u]]<siz[v])son[u]=v;
  }
}
void dfs2(int u,int t){ //搞top
  top[u]=t;  //记录链头
  if(!son[u]) return; //无重儿子
  dfs2(son[u],t);     //搜重儿子
  for(int v:e[u]){
    if(v==fa[u]||v==son[u])continue;
    dfs2(v,v); //搜轻儿子
  }
}
int lca(int u,int v){
  while(top[u]!=top[v]){
    if(dep[top[u]]<dep[top[v]])swap(u,v);
    u=fa[top[u]];
  }
  return dep[u]<dep[v]?u:v;
}
void add(int u,int v){
	e[u].push_back(v);
}
struct edges{
	int u,v,w;
	bool operator<(const edges &tmp){
		return w>tmp.w;
	}
};
edges edge[M];
void solve(){
	int q;
	cin>>n>>m;
	DSU dsu(2*n+1);
	for(int i=1;i<=m;i++){
		int u,v,w;
		cin>>u>>v>>w;
		//原图根本不要了
		//e[u].push_back(v);
		//e[v].push_back(u);
		edge[i]={u,v,w};
	}
	sort(edge+1,edge+1+m);
	int cnt=n;
	for(int i=1;i<=m;i++){
		auto [u,v,w]=edge[i];
		//cerr<<w<<endl;
		u=dsu.find(u),v=dsu.find(v);
		if(u!=v){
			cnt++;
			//这里合并的时候注意父节点
			dsu.merge(cnt,u);
			dsu.merge(cnt,v);
			add(cnt,u);add(u,cnt);
			add(cnt,v);add(v,cnt);
			a[cnt]=w;
		}
	}
	//build
	
	
	//lca
	for(int i=1;i<=2*n;i++){
		if(dsu.find(i)==i){
			dfs1(i,0);
			dfs2(i,i);
		}
	}
    cin>>q;
	for(int i=1;i<=q;i++){
		int u,v;cin>>u>>v;
		if(dsu.find(u)!=dsu.find(v)){
			cout<<-1<<endl;
			continue ;
		}
		int mid=lca(u,v);
		
		cout<<a[mid]<<endl;
	}
}

https://hydro.ac/d/bzoj/p/3732求图中连通两点路径中的最大边的最小值

与上面的题目非常相似,只需要排序时边权从小到大排从而建立重构树

[bzoj 3551 ONTAK2010]Peaks加强版 kruskal重构树+主席树-CSDN博客

https://hydro.ac/d/bzoj/p/3545

posted @ 2024-05-18 15:57  potential-star  阅读(140)  评论(0)    收藏  举报