int fa[N];/*初始化*///一定记着初始化voidinit(int n){for(int i =0; i <= n; i++) fa[i]= i;}/*查询*/intfind(int x){return(fa[x]== x)? x : fa[x]=find(fa[x]);}/*合并*///能用并查集操作的东西,还不少!voidmerge(int x,int y){
x =find(x), y =find(y);if(x != y) fa[x]= y;}
// 维护并查集的大小的时候的mergevoidinit(int n){for(int i =1; i <= n; i++) sz[i]=1;}voidmerge(int x,int y){int fx =find(x), fy =find(y);if(fx != fy){
sz[fy]+= sz[fx];
sz[fx]=0;
fa[fx]= fy;}}
按秩优化:虽然没啥用,但最好知道原理
//按秩优化,虽然没啥用,但我最好知道其原理voidinit(int n){for(int i =1; i <= n; i++) dep[i]=0;}voidmerge(int x,int y){int fx =find(x), fy =find(y);if(dep[fx]< dep[fy])
fa[fx]= fy;//小的挂在大的上,不然大的还是会增加else{
fa[fy]= fx;if(fx == fy)return;//本来就是一个东西,dep就不要增加了if(dep[fx]== dep[fy]) dep[fx]++;//谁是根节点得分清楚}}
二、基础算法
1.克鲁斯卡尔:加n-1条边
时间复杂度:
O
(
e
log
n
)
O(e\log n)
O(elogn)
操作:先对边进行从小到达排序,然后遍历加边(并查集相同则不加),直到加到
n
−
1
n-1
n−1条边为止。
代码:略了,很简单的。
2.prim:加n-1个点(不算第一个点)
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
模板:
int vis[maxn], d[maxn];intprim(){for(int i =1; i <= n; i++) vis[i]=0, d[i]=9e18;//初始化int ans =0;for(int i =1; i <= n; i++){int t =-1;for(int j =1; j <= n; j++){if(vis[j]==0&&(t ==-1|| d[j]< d[t])) t = j;}
vis[t]=1;//标记if(i >1) ans += d[t];//第一个点不加,i==1是d[t]=1e9for(int j =1; j <= n; j++)
d[j]=min(d[j], mp[t][j]);//二维数组注意空间复杂度}return ans;}
3.Boruvka算法(结合prim与克鲁斯卡尔)
时间复杂度是严格的
O
(
e
log
n
)
O(e\log n)
O(elogn)。每次最少可以连连通块数量少一半,最多调用
log
n
\log n
logn次Boruvka,每次复杂度为
O
(
e
log
n
)
O(e\log n)
O(elogn)(
log
n
\log n
logn为并查集)。
boolBoruvka(){// best[u]=id记录u-v,id为u-v的下标。其中u,v不在同一连通块,且u-v是所有满足条件的最小权值边。int best[maxn];memset(best,0,sizeof(best));//枚举所有边,更新best。(与Prim差不多,不同的是这里是多个连通块拓展增广路,prim是一个连通块)for(int i =1; i <= m; i++){int fx =find(s[i].u), fy =find(s[i].v);if(fx == fy)continue;if(s[i].w < s[best[fx]].w || best[fx]==0) best[fx]= i;if(s[i].w < s[best[fy]].w || best[fy]==0) best[fy]= i;}//枚举所有s[best[i]]边,判断是否能够连线(与克鲁斯卡尔完全一样的操作)bool f =false;for(int i =1; i <= n && cx +1< n; i++){int fx =find(s[best[i]].u), fy =find(s[best[i]].v);if(fx == fy)continue;merge(fx, fy);
ans += s[best[i]].w;
cx++;
f =true;//表示还可能继续更新,如果!f一定不能继续更新}return f;
题意:给定
1
≤
n
≤
1
e
3
1\le n\le 1e3
1≤n≤1e3朵云,
1
≤
m
≤
1
e
4
1\le m\le 1e4
1≤m≤1e4条线(下标为
u
u
u,
v
v
v的云相连的代价为dis),求这
n
n
n朵云通过这些线连接成
k
k
k朵的最小花费。
题解:克鲁斯卡尔。遍历所有边如果连线条数为
n
−
k
n-k
n−k则说明有答案,否则不能连成
k
k
k条边。
代码:
#include<bits/stdc++.h>// #define int long long#defineread(x)scanf("%d",&x)#defineprint(a, c)printf("%d%c", a, c)#definedbg(x) cout << #x <<">>>"<< x << endl;usingnamespace std;constint maxn =1e4+10;structnode{int u, v, dis;node(){}node(int _u,int _v,int _dis){ u = _u, v = _v, dis = _dis;}voidout(int i){
cout <<">>>"<< i <<":::"<< u <<" "<< v <<" "<< dis << endl;}booloperator<(node b){return dis < b.dis;}} s[maxn];int n, m, k;int fa[maxn], dep[maxn];intfind(int x){return(x == fa[x])? x : fa[x]=find(fa[x]);}voidmerge(int x,int y){int fx =find(x), fy =find(y);if(dep[fx]< dep[fy])
fa[fx]= fy;else{
fa[fy]= fx;if(dep[fx]== dep[fy]) dep[fy]++;}}signedmain(){read(n),read(m),read(k);for(int i =1; i <= m; i++){read(s[i].u),read(s[i].v),read(s[i].dis);}sort(s +1, s +1+ m);for(int i =1; i <= n; i++) fa[i]= i;int ans =0, cx =0;for(int i =1; i <= m && cx + k < n; i++){int fu =find(s[i].u), fv =find(s[i].v);if(fu != fv)merge(fu, fv), ans += s[i].dis, cx++;}if(k > n || cx + k != n)puts("No Answer");elseprint(ans,'\n');return0;}
先求最小生成树T,然后枚举所有在T上的边,求
m
i
n
(
T
−
w
+
w
′
)
min(T-w+w')
min(T−w+w′),
w
w
w为T上
u
−
v
u-v
u−v路径上的最大值,
w
′
w'
w′为途中
u
−
v
u-v
u−v其他所有路径上的最小的最大边(非严格:
w
′
≥
w
w'\ge w
w′≥w,严格:
w
′
>
w
w'>w
w′>w)。
时间复杂度:
O
(
m
l
o
g
m
+
n
m
)
O(mlogm+nm)
O(mlogm+nm)。
n
m
nm
nm为求所有
u
−
v
u-v
u−v路径的最小的最大值的复杂度。一般不是用这种方式,因为不容易优化。
解法2:
先求最小生成树T,然后枚举所有不在T上的边,求
m
i
n
(
T
−
w
+
w
′
)
min(T-w+w')
min(T−w+w′)。
w
′
w'
w′为枚举的这条边,
w
w
w为T上
u
−
v
u-v
u−v路径上的最大值。
时间复杂度:
O
(
m
l
o
g
m
+
n
n
)
O(mlogm+nn)
O(mlogm+nn)。
n
n
nn
nn为求T上所有
u
−
v
u-v
u−v路径的最大值的复杂度(每个点作为祖先dfs一边即可),可以LCA倍增优化,优化后时间复杂度:
O
(
m
l
o
g
m
)
O(mlogm)
O(mlogm)。
注意:用方法二求严格最小生成树,需要预处理T上
u
−
v
u-v
u−v路径上的最大值以及严格次大值(不能等于最大值),如果枚举的这条边大于最大值,就替换最大值,等于的话就替换严格次大值。
题意:给定
n
n
n个点(
n
≤
1.5
e
4
n\le 1.5e4
n≤1.5e4),
m
m
m条边(
m
≤
3
e
4
m\le 3e4
m≤3e4)。
q
q
q次询问,每次询问这个无向图上任意两个点的最小瓶颈路,即这两点之间的所有路径中最大值的最小值(最小生成树)。
解法1:爆改一下上面次小生成树的代码(好像不太会emm,略了)
解法2:kruskal重构树(建最小生成树,然后求两点之间最大边权)
#include<bits/stdc++.h>#defineintlonglong#defineread(x)scanf("%lld",&x)#defineprint(a, c)printf("%lld%c", a, c)#definepbpush_back#definedbg(x) cout << #x <<">>>"<< x << endlusingnamespace std;constint maxn =3e4+10;structnode{int x, y, z;node(){}node(int _x,int _y,int _z){ x = _x, y = _y, z = _z;}booloperator<(node b)const{return z < b.z;}voidout(int i){ cout << i <<":::"<< x <<" "<< y <<" "<< z << endl;}} s[maxn];int n, m, q, x, y, z;int fa[maxn], dep[maxn], cnt, cx;int val[maxn], id;// kruskal重构树大小为2*n-1
vector<int> g[maxn];int f[maxn][25];intfind(int x){return(x == fa[x])? x : fa[x]=find(fa[x]);}voiddfs(int x,int fa){for(auto i : g[x]){if(i == fa)continue;
dep[i]= dep[x]+1, f[i][0]= x;dfs(i, x);}}intget_lca(int x,int y){if(dep[x]< dep[y])swap(x, y);for(int j =20; j >=0; j--)if(dep[f[x][j]]>= dep[y]) x = f[x][j];if(x == y)return x;for(int j =20; j >=0; j--)if(f[x][j]!= f[y][j]) x = f[x][j], y = f[y][j];
x = f[x][0], y = f[y][0];return x;}signedmain(){read(n),read(m),read(q);for(int i =1; i <= m; i++){read(x),read(y),read(z);if(x == y)continue;
s[++cnt]=node(x, y, z);}sort(s +1, s +1+ cnt);// for (int i = 1; i <= cnt; i++) s[i].out(i);//注意初始化2*n-1个节点for(int i =1; i <=2* n -1; i++) fa[i]= i;for(int i =1; i <= cnt && cx +1< n; i++){int fu =find(s[i].x), fv =find(s[i].y);if(fu != fv){
cx++;
id = n + cx;//新节点下标
val[id]= s[i].z;//不需要特别排序,就按照加入的顺序
fa[fu]= id, fa[fv]= id;
g[id].pb(fu), g[id].pb(fv);}}//注意要以2*n-1为根节点
dep[2* n -1]=1;dfs(2* n -1,0);// lca_init:::尽量写在下面????居然忘了调用for(int j =1; j <=20; j++)for(int i =1; i <=2* n -1; i++) f[i][j]= f[f[i][j -1]][j -1];while(q--){read(x),read(y);print(val[get_lca(x, y)],'\n');}return0;}