一类以并查集在建树过程中维护各种信息的值——克鲁斯卡尔重构树前身
第一次见到是在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
标签:重构,int,siz,路径,克鲁斯,卡尔,权值,find From: https://www.cnblogs.com/mathiter/p/18199411