算法学习笔记之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;
}
posted @ 2022-03-19 09:29  SSZX_loser_lcy  阅读(39)  评论(0编辑  收藏  举报