算法学习笔记之kruskal重构树
前言
作为OIer中的精英,相信各位在初学时都学过简单(毒瘤)的kruskal最小生成树算法。
它可以在O(nlogn)的时间复杂度中构造出一颗最小生成树,在保证连通性的情况下使得边权和最小。
然而,我们还有一种对其的使用方法:
当我们把边排好顺序,从小到大一次枚举,合并两个节点就建一个新节点作为他们的父亲,节点权值为边的权值。
这样建出的树具有优良的性质,每两个点的LCA的权值为他们路径上最小边的最大权值,而叶子节点为原节点,非叶子节点为新建的节点。
运用它的优良性质,我们就可以将路径最值迎刃而解了!
例题
1.[NOIP2013 提高组] 货车运输
一道非常经典的生成树问题,有多种解法。
1.最大生成树上跳LCA求最值:
先把最大生成树建出,然后在找两个点LCA时维护最小值,最后输出。时间复杂度O(nlogn);
代码:
点击查看代码
#include<bits/stdc++.h>
#define in read()
using namespace std;
const int N=1e5+100;
const int M=5e5+100;
int n,m,q;
int fa[N];
int find(int x){
if(fa[x]==x){
return x;
}
return fa[x]=find(fa[x]);
}
struct edge{
int x,y,val;
}e[M];
bool cmp(edge a,edge b){
return a.val>b.val;
}
int head[N],to[M],nxt[M],w[M],cnt;
void add(int u,int v,int z){
nxt[++cnt]=head[u];
head[u]=cnt;
w[cnt]=z;
to[cnt]=v;
}
int f[N][30];
int minn[N][30];
int dep[N],vis[N];
void pre(int u,int father){
for(int i=1;i<=20;i++){
f[u][i]=f[f[u][i-1]][i-1];
minn[u][i]=min(minn[u][i-1],minn[f[u][i-1]][i-1]);
}
vis[u]=1;
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v==father||vis[v]) continue;
dep[v]=dep[u]+1;
minn[v][0]=w[i];
f[v][0]=u;
pre(v,u);
}
}
inline int LCA(int x,int y){
int ans=0x3f3f3f3f;
if(dep[x]<dep[y]){
swap(x,y);
}
for(int i=20;i>=0;i--){
if(dep[f[x][i]]>=dep[y]){
ans=min(ans,minn[x][i]);
x=f[x][i];
}
if(x==y){
return ans;
}
}
for(int i=20;i>=0;i--){
if(f[x][i]!=f[y][i]){
ans=min(ans,min(minn[y][i],minn[x][i]));
x=f[x][i];
y=f[y][i];
}
}
ans=min(ans,min(minn[x][0],minn[y][0]));
return ans;
}
inline int read(){
static char ch;
int res=0;
while((ch=getchar())<'0'||ch>'9');
res=ch-'0';
while((ch=getchar())>='0'&&ch<='9') res=res*10+ch-'0';
return res;
}
inline void kruskal(){
for(int i=1;i<=n;i++){
fa[i]=i;
}
sort(e+1,e+m+1,cmp);
for(int i=1;i<=m;i++){
int x=find(e[i].x);
int y=find(e[i].y);
if(x!=y){
fa[x]=y;
add(e[i].x,e[i].y,e[i].val);
add(e[i].y,e[i].x,e[i].val);
}
}
return ;
}
int main(){
n=in,m=in;
for(int i=1;i<=m;i++){
e[i].x=in,e[i].y=in,e[i].val=in;
}
kruskal();
for(int i=1;i<=n;i++){
if(!vis[i]){
dep[i]=1;
f[i][0]=i;
minn[i][0]=0x3f3f3f3f;
pre(i,i);
}
}
q=in;
for(int i=1;i<=q;i++){
int x,y;
x=in,y=in;
if(find(x)!=find(y)){
cout<<-1<<endl;
continue;
}
int lca=LCA(x,y);
cout<<lca<<endl;
}
return 0;
}
2.kruskal重构树解法
结合上面提到的重构树的优良性质,我们只需要把边从大到小放入重构树中。这样生成的重构树满足两点之间的LCA的权值为路径上最小值的最大。也就满足此题货物运输的要求。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
struct edge{
int u, v, w;
bool operator < (const edge &x){
return w > x.w; //从大到小排序
}
}e[N];
vector<int> g[N]; //vector存图
int n, m, q, cnt, fa[N], vis[N], val[N], d[N], f[N][30];
int find(int x){
return x == fa[x] ? x : fa[x] = find(fa[x]);
} //并查集维护祖先
void kruskal(){
sort(e + 1, e + m + 1);
for(int i = 1; i <= n; i++) fa[i] = i;
for(int i = 1; i <= m; i++){
int fu = find(e[i].u), fv = find(e[i].v);
if(fu != fv){
val[++cnt] = e[i].w;
fa[cnt] = fa[fu] = fa[fv] = cnt;
g[cnt].push_back(fu); g[cnt].push_back(fv);
g[fu].push_back(cnt); g[fv].push_back(cnt);
}
}
}
void dfs(int u, int fa){
d[u] = d[fa] + 1, f[u][0] = fa, vis[u] = 1;
for(int i = 1; (1 << i) <= d[u]; i++) //倍增数组维护
f[u][i] = f[f[u][i-1]][i-1];
for(int i = 0; i < g[u].size(); i++){
int v = g[u][i];
if (v != fa) dfs(v, u);
}
}
int lca(int x, int y){
if (d[x] < d[y]) swap(x, y);
int k = d[x] - d[y];
for(int i = 25; i >= 0; i--)
if((1 << i) & k) x = f[x][i];
if(x == y) return x;
for(int i = 25; i >= 0; i--)
if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
return f[x][0];
}
int main(){
// freopen("truck.in", "r", stdin);
// freopen("truck.out", "w", stdout);
cin >> n >> m; cnt = n;
for(int i = 1; i <= m; i++) cin >> e[i].u >> e[i].v >> e[i].w;
kruskal();
for(int i = 1; i <= cnt; i++){
if(!vis[i]){
int f = find(i);
dfs(f, 0);
}
}
cin >> q;
while(q--){
int u, v;
cin >> u >> v;
if (find(u) != find(v)) cout << -1 << endl;
else cout << val[lca(u, v)] << endl;
}
return 0;
}
2.BZOJ3732 Network
解法:
同是kruskal重构树的模板题,与上一题相似。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5;
const int M=6e5+300;
struct edge{
int u,v,w;
}e[M];
int ch[N][2],fa[N],f[N][30],val[N],cnt,dep[N];
int n,m,k;
bool cmp(edge x,edge y){
return x.w<y.w;
}
int find(int x){
if(fa[x]==x) return fa[x];
else return fa[x]=find(fa[x]);
}
void Kruskal(){
sort(e+1,e+m+1,cmp);
for(int i=1;i<=m;i++){
int x=e[i].u;
int y=e[i].v;
int fx=find(x);
int fy=find(y);
if(fx==fy) continue;
ch[++cnt][0]=fx,ch[cnt][1]=fy;
fa[fa[x]]=fa[fa[y]]=f[fa[x]][0]=f[fa[y]][0]=cnt;
fa[x]=fa[y]=cnt;
val[cnt]=e[i].w;
}
}
void dfs(int pos){
if(!ch[pos][0]&&!ch[pos][1]){
return ;
}
dep[ch[pos][1]]=dep[ch[pos][0]]=dep[pos]+1;
dfs(ch[pos][0]);
dfs(ch[pos][1]);
}
int LCA(int x,int y){
if(dep[x]<dep[y]){
swap(x,y);
}
for(int i=20;i>=0;i--){
if(dep[f[x][i]]>=dep[y]){
x=f[x][i];
}
}
if(x==y){
return y;
}
for(int i=20;i>=0;i--){
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
int main(){
cin>>n>>m>>k;
cnt=n;
for(int i=1;i<=2*n;i++){
fa[i]=i;
}
for(int i=1;i<=m;i++){
cin>>e[i].u>>e[i].v>>e[i].w;
}
Kruskal();
dfs(cnt);
for(int i=1;i<=20;i++){
for(int j=1;j<=2*n;j++){
f[j][i]=f[f[j][i-1]][i-1];
}
}
for(int i=1;i<=k;i++){
int x,y;
cin>>x>>y;
cout<<val[LCA(x,y)]<<endl;
}
return 0;
}
3.[NOI2018] 归程
哦~~~就是这道题!他让SPFA的时代终结了!Dij的时代到来了!
思路如下:
考虑每一条边的海拔高度,先从大到小排序,然后kruskal构建一颗重构树。
在这颗重构树上,每一个新建的子节点的权值是它的子树中海拔最低的边的海拔。
只要降雨量小于其权值那么它子树中的节点都可以乘车到到达,对路程的奉献为0。
我们就从出发点不断倍增,找到海拔刚好小于积水线的点,然后返回它的子树中到原点的最小距离。因为其子树中的所有点都可以开车到达。
最小距离一开始就预处理好(注意SPFA),在重构的时候合并最小值。
代码:
点击查看代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define re register
using namespace std;
inline int read(){
int x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x;
}
const int N=4e5+10;
int T,n,m,last;
struct node{
int u,v,l,a,nxt;
bool operator < (const node &b) const{
return a>b.a;
}
}e[N<<1],tmp[N<<1],edge[N<<1];
int head[N],tot,dis[N];
struct heap{
int x,dis;
bool operator < (const heap &b) const{return dis>b.dis;}
};
int f[N],cnt;
inline void Add(int x,int y,int z){
edge[++tot].v=y,edge[tot].l=z,edge[tot].nxt=head[x];
head[x]=tot;
}
inline void dijkstar(){
priority_queue <heap> q;
memset(dis,0x3f,sizeof dis);
dis[1]=0;
q.push((heap){1,0});
while(!q.empty()){
heap now=q.top();
q.pop();
int x=now.x;
if(dis[x]<now.dis) continue;
for(re int i=head[x];i;i=edge[i].nxt){
int y=edge[i].v;
if(dis[y]>dis[x]+edge[i].l){
dis[y]=dis[x]+edge[i].l;
q.push((heap){y,dis[y]});
}
}
}
}
inline int find(int x){
return f[x]==x?x:f[x]=find(f[x]);
}
inline void add(int x,int y){
edge[++tot].v=y,edge[tot].nxt=head[x];
head[x]=tot;
}
inline void kruskal(){
sort(e+1,e+m+1);
for(re int i=1;i<=n;i++){
f[i]=i;
}
cnt=n;
int num=0;
for(re int i=1;i<=m;i++){
int fu=find(e[i].u),fv=find(e[i].v);
if(fu!=fv){
num++;
tmp[++cnt].a=e[i].a;
f[fu]=f[fv]=f[cnt]=cnt;
add(cnt,fu),add(cnt,fv);
}
if(num==n-1) break;
}
}
int fa[N][20],dep[N];
inline void dfs(int x,int p){
dep[x]=dep[p]+1;
fa[x][0]=p;
for(re int i=1;i<=19;i++){
fa[x][i]=fa[fa[x][i-1]][i-1];
}
for(re int i=head[x];i;i=edge[i].nxt){
int y=edge[i].v;
dfs(y,x);
tmp[x].l=min(tmp[x].l,tmp[y].l);
}
}
inline int query(int x,int y){
for(re int i=19;i>=0;i--){
if(dep[x]-(1<<i)>0&&tmp[fa[x][i]].a>y){
x=fa[x][i];
}
}
return tmp[x].l;
}
inline void solve(){
kruskal();
dfs(cnt,0);
int q=read(),k=read(),s=read();
while(q--){
int x=(k*last+read()-1)%n+1,y=(k*last+read())%(s+1);
printf("%d\n",last=query(x,y));
}
}
inline void init(){
memset(head,0,sizeof head);
memset(fa,0,sizeof fa);
memset(f,0,sizeof f);
memset(tmp,0,sizeof tmp);
memset(edge,0,sizeof edge);
last=tot=0;
}
int main(){
T=read();
while(T--){
init();
n=read(),m=read();
for(int i=1;i<=m;i++){
e[i].u=read(),e[i].v=read(),e[i].l=read(),e[i].a=read();
Add(e[i].u,e[i].v,e[i].l);
Add(e[i].v,e[i].u,e[i].l);
}
dijkstar();
for(re int i=1;i<=n;i++) tmp[i].l=dis[i];
for(re int i=n+1;i<=n<<1;i++) tmp[i].l=INF;
memset(head,0,sizeof head),tot=0;
solve();
}
return 0;
}
4.P4197 Peaks
蒟蒻打了一天调了一天还是没有调出来……
本题思路是kruskal+主席树求区间第k大。
先把边从小到大排序,然后重构。
当在dfs预处理时,每一次扫到一个叶子节点就加入主席树中,注意要先离散化。
同时记录每一个节点最左端的叶子节点编号,以及最右端的叶子节点编号,方便主席树查询。
在每一次询问时,先往上倍增找到所有能走到的山峰集合,然后再利用主席树查询k大值。
代码:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10;
const int M=5e5+10;
struct Node{
int from,to,val;
}g[M];
struct Edge{
int to,next;
}f[M<<1];
int n,m,q,fa[N],h[N][25],v[N],cnt,tot,head[N],rt[N<<5],ls[N<<5],rs[N<<5],a[N],b[N],sz,num,range[N][2],sum[N<<5];
bool cmp(Node a,Node b){
return a.val<b.val;
}
int find(int a){
if(fa[a]==a) return a;
else return fa[a]=find(fa[a]);
}
void add(int u,int v){
f[++cnt].to=v;
f[cnt].next=head[u];
head[u]=cnt;
}
//重构
void kruskal()
{
for (int i = 1; i <= 2 * n; i ++) fa[i] = i;
sort(g + 1,g + 1 + m,cmp);
tot = n;
for (int i = 1; i <= n; i ++)
{
int x = find(g[i].from),y = find(g[i].to);
if (x == y) continue;
fa[x] = fa[y] = ++ tot,v[tot] = g[i].val;
add(tot,x),add(tot,y);
h[x][0] = h[y][0] = tot;
}
}
//主席树
void build(int &x,int l,int r)
{
x = ++ cnt;
if (l == r) return;
int mid = l + r >> 1;
build(ls[x],l,mid),build(rs[x],mid + 1,r);
}
void modify(int pre,int &rt,int l,int r,int k)
{
rt = ++ cnt;
sum[rt] = sum[pre] + 1;
if (l == r) return;
int mid = l + r >> 1;
if (k <= mid) rs[rt] = rs[pre],modify(ls[pre],ls[rt],l,mid,k); else ls[rt] = ls[pre],modify(rs[pre],rs[rt],mid + 1,r,k);
}
int query(int x,int y,int l,int r,int k)
{
if (l == r) return l;
int mid = l + r >> 1,q = sum[rs[y]] - sum[rs[x]];
if (k <= q) return query(rs[x],rs[y],mid + 1,r,k); else return query(ls[x],ls[y],l,mid,k - q);
}
//维护信息
void dfs(int u)
{
for (int i = 1; i <= 20; i ++) h[u][i] = h[h[u][i - 1]][i - 1];
range[u][0] = num;
if (!head[u])
{
int x = lower_bound(b + 1,b + 1 + sz,a[u]) - b;
range[u][0] = ++ num;
modify(rt[num - 1],rt[num],1,sz,x);
return;
}
for (int i = head[u]; i; i = f[i].next)
dfs(f[i].to);
range[u][1] = num;
}
int read()
{
int x = 0,w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * w;
}
signed main(){
cin>>n>>m>>q;
for(int i=1;i<=n;i++)
{
cin>>a[i];
b[i]=a[i];
}
sort(b+1,b+n+1);
sz=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=m;i++){
cin>>g[i].from>>g[i].to;
cin>>g[i].val;
}
kruskal();
cnt=0;
build(rt[0],1,sz);
dfs(tot);
int lans=0;
while(q--){
int x,y,z;
cin>>x>>y>>z;
// cout<<x<<' '<<y<<' '<<z<<endl;
for(int i=20;i>=0;i--){
if(h[x][i]&&v[h[x][i]]<=y) x=h[x][i];
}
if(range[x][1]-range[x][0]<z) {
printf("%lld\n",-1ll);
lans=0;
continue;
}
else {
printf("%lld\n",lans=b[query(rt[range[x][0]],rt[range[x][1]],1,sz,z)]);
}
// cout<<lans<<endl;
}
return 0;
}