ZR最小生成树练习题

最小生成树练习题

CF 891C

题目传送

题意

给定无向图,和K个询问,每个询问给定一个边集,求这个边集里的边是否是同一棵最小生成树里的边

思路

  • 首先,我们应该明确。
  • 如果一条边权为 \(w\) 的边,在边权小于 \(w\) 的边都尝试加入,原图已连通时,则这条边一定不会出现在 \(MST\) 中,否则则可能出现在 \(MST\)
  • 且对于边权相同的一些边我们
  • 那么,我们可以考虑如果只有一个询问,我们把询问中的边从小到大排序并去重,对于当前的一条边权为 \(w\) 的边,我们用原图中边权小于 \(w\) 的边建一个森林或树。将这条边加入后如果出现环,那这条边一定不在最小生成树中,这个询问也就是 \(False\)
  • 对于多组询问,每次询问都要保证是原图的联通性,那就要求并查集要有撤销操作。
  • 我们可以按边权排序,对于存在这种边的询问依次判断。每次询问加完边后都要撤销
  • 所以,并查集要支持撤销,可以按秩合并

小结

  • 并查集按秩合并和撤销操作

代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
#include <utility>
#define M 500005
#define N 100005
using namespace std;
int n,m,k,q,top;
int fa[M],stk[M],s[M],ans[M];
struct node{
	int id,from,to,val;
}e[M];
vector<node> a[M];
bool cmp(node a,node b){
	return a.val <b.val ;
}
int read(){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){s=s*10+ch-'0',ch=getchar();}
	return s*w;
}
int find(int x){
	while(x!=fa[x]) x=fa[x];
	return x;
}
bool unin(int x,int y){
	int fx=find(x),fy=find(y);
	if(fx==fy) return 0;
	if(s[fx]>s[fy]) swap(fx,fy);
	fa[fx]=fy;
	s[fy]+=s[fx];
	stk[++top]=fx;
	return true;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		e[i].from =a,e[i].to =b,e[i].val =c;
	}
	scanf("%d",&q);
	for(int i=1;i<=q;i++){
		scanf("%d",&k);
		for(int j=1;j<=k;j++){
			int x;
			scanf("%d",&x);
			a[e[x].val].push_back({i,e[x].from,e[x].to,e[x].val});
		}
	}
	sort(e+1,e+m+1,cmp);
	for(int i=1;i<=n;i++) fa[i]=i,s[i]=1;
	for(int i=1;i<=m;i++){
		int val=e[i].val ;
		top=0;
		for(int j=0;j<a[val].size();j++){
			if(ans[a[val][j].id]) continue;
			if(j>0&&a[val][j].id!=a[val][j-1].id){
				while(top){
					int x=stk[top--];
					s[fa[x]]-=s[x];
					fa[x]=x;
				}
			}
			if(!unin(a[val][j].from,a[val][j].to)) ans[a[val][j].id]=1;
		}
		while(e[i].val ==val) unin(e[i].from ,e[i].to),i++;
		if(e[i].val!=val) i--;
	}
	for(int i=1;i<=q;i++) 
	  if(ans[i]) printf("NO\n");
	  else printf("YES\n");
	return 0;
}

CF125E

题目传送

题意

  • 给定 \(N\)个点 \(M\) 条边的无向图,求出 \(1\) 号点出度为 \(k\) 的最小生成树
  • \(N<=5000,M<=10^5\)

思路

  • 我们考虑先跑一遍最小生成树,如果1号点的度数是k,那么直接得到答案
  • 如果度数<k,我们可以把1号点连的边边权统一减掉\(x\)
  • 反之,如果度数\(>k\) ,我们则加上 \(x\)
  • 那么,这个x取多少是合适的呢
  • 由于\(x\)的取值具有单调性
  • 我们可以考虑二分 \(x\)
  • 但是有一个问题,最小生成树的形态不一,也就是说可能有与一号点连的边权值相同的边。我们应选与一号点连\(k\) 条边的那一条,但这不好操作。我们多二分几次,调整x使得生成的最小生成树恰好满足选与1号点相连的边。

小结

  • 求最小度限制生成树,二分增量

Bug

  • 写二分的时候注意 \(l,r\) 的取值

代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define M 100005
using namespace std;
int n,m,k,res,jus;
int fa[5005],ton[5005];
struct node{
	int from,to,id;
	int val;
	bool tag;
}e[M],oge[M];
int read(){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){s=s*10+ch-'0',ch=getchar();}
	return s*w;
}
int find(int x){
	if(fa[x]==x) return x;
	return fa[x]=find(fa[x]);
}
bool cmp(node a,node b){
	if(a.val==b.val) return a.from <b.from ;
	return a.val <b.val ;
}
int SPFA(){
	int res=0;ton[0]=0;
	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].from) ,y=find(e[i].to) ;
		if((x==y)||(e[i].from ==1&&res==k)) continue;
		fa[x]=y;
		if(e[i].from ==1) res++;
		ton[++ton[0]]=e[i].id ;
	}
	return res;
}
bool check(int x){
	for(int i=1;i<=m;i++) {
		e[i]=oge[i];
		if(oge[i].from ==1) e[i].val +=x;
	}
	int now=SPFA();
	if(now==k) return 1;
	return 0;
}
int main()
{
	n=read();m=read();k=read();
	for(int i=1;i<=m;i++){
		oge[i].id=i,oge[i].from =read(),oge[i].to =read(),oge[i].val=read();
		if(oge[i].from> oge[i].to){
			int tep=oge[i].from ;
			oge[i].from=oge[i].to ;
			oge[i].to =tep;
		}
	}
	sort(oge+1,oge+m+1,cmp);
    int l=-100000,r=100000,ans;
	while(l<=r){
		int mid=(l+r)/2;
		if(check(mid)) l=mid+1,ans=mid;
		else r=mid-1;
//    	if(check(mid)==k) {ans=mid;break;}
//    	else if(check(mid)<k) r=mid-1;
//		else if(check(mid)>k) l=mid+1,ans=mid;
        
//    	printf("%d %d %d %d\n",l,mid,r,check(mid));
//		cout<<ans<<" "<<check(ans)<<endl;
	}
	if(!check(ans)||(ton[0]!=n-1)){printf("-1\n");return 0;}
	printf("%d\n",n-1);
	for(int i=1;i<=ton[0];i++) printf("%d ",ton[i]);
//	for(int i=1;i<=m;i++) 
//	  if(e[i].tag){printf("%d ",e[i].id); 
//	  	printf("%d %d %d\n",e[i].id,e[i].from ,e[i].to);
//	  }
	return 0;
}

BZOJ 3732(Kruskal重构树

题目传送

题意

给出一张 \(N\) 个点 \(M\) 条边的无向图。
\(Q\) 组询问,每组询问两点 \((s,t)\) 间,所有路径当中最长边的最小值是多少。

思路

  • 必然是走在最小瓶颈生成树上的路径。
  • 那么可以求出最小生成树后支持查找树上路径最大值。
  • 传统做法树剖、倍增均可。
  • 若使用 \(Kruskal\) 重构树,两点间路径最大值即为重构树上的两点 \(LCA\)

Kruskal重构树

  • \(Kruskal\) 实现过程中, 在使用并查集合并两个集合(两棵树)的根时,我们新建一个点权为连接这两个集合的边权大小的节点作为树根。
  • 性质:
  • 最小生成树上两点间边权最大值为重构树上的LCA的点权
  • 重构树是二叉树,点数是 \(2n-1\) ,边数是 \(4n-2\) ,深度是 \(n\)
  • 整棵树是一个大根堆

Bug

  • 重构树的边数应该是点数的4倍,边数的2倍
  • 我又开小了一半

小结

  • \(kruskal\) 重构树的空间一定要开够

代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define N 30005
#define M 30005
using namespace std;
int n,m,k,res,tot;
int head[N],fa[N],f[N][21],a[N],dep[N];
struct node{
	int from,to,net,val;
}e[M<<1],mp[M];
void add(int a,int b){
	e[++tot].to=b,e[tot].net =head[a],head[a]=tot;
	e[++tot].to=a,e[tot].net =head[b],head[b]=tot;
}
bool cmp(node a,node b){
	return a.val <b.val ;
}
int find(int x){
	if(x==fa[x]) return x;
	return fa[x]=find(fa[x]);
}
void work(){
	for(int i=1;i<2*n;i++) fa[i]=i;
	for(int i=1;i<=m;i++){
		int u=mp[i].from ,v=mp[i].to ,w=mp[i].val ;
		int fu=find(u),fv=find(v);
		if(fu==fv) continue;
//		cout<<fu<<" "<<fv<<" ";
		++res;
		fa[fu]=res;
		fa[fv]=res;
//		cout<<fa[fu]<<" "<<fa[fv]<<endl;
		a[res]=w;
		add(fu,res);
		add(fv,res);
		f[fu][0]=res;
		f[fv][0]=res;
//		cout<<"D"<<endl;
		if(res==2*n-1) break;
    }
}
void dfs(int x,int last){
	for(int i=head[x];i;i=e[i].net){
		int to=e[i].to ;
		if(to==last) continue;
		dep[to]=dep[x]+1;
		dfs(to,x);
	}
}
void pre(){
	for(int j=1;j<=19;j++)
	  for(int i=1;i<=res;i++) f[i][j]=f[f[i][j-1]][j-1];
}
int LCA(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=19;i>=0;i--)
	  if(dep[f[x][i]]>=dep[y]) x=f[x][i];
	if(x==y) return x;
	for(int i=19;i>=0;i--)
	  if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
	return f[x][0];
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=m;i++){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		mp[i].from=a,mp[i].to=b,mp[i].val =c;
	}
	sort(mp+1,mp+m+1,cmp);
	res=n;
	work();
    dep[res]=1;
	dfs(res,0);
//	f[res][0]=res;//<dep[i]<<" "<<f[i][0]<<" "
//	for(int i=1;i<=res;i++) cout<<a[i]<<endl;
//	cout<<endl;
//	cout<<"De"<<endl;
	pre();
	while(k--){
		int u,v;
		scanf("%d%d",&u,&v);
//		cout<<u<<" "<<v<<" "<<LCA(u,v)<<endl;
		printf("%d\n",a[LCA(u,v)]);
	}
	return 0;
}

归程-pre

题意

给定 \(N\) 个点 \(M\) 条边的无向图
\(k\) 组询问。每次问从一个点 \(s_i\)出发,只允许经过边权\(<=w_i\)的边,能够到达的点权第\(k_i\)小的点。

思路

  • 考虑暴力从每个起点出发bfs,能够走到的边就是一个连通块。答案是这个连通块中点权第 \(k_i\) 小的点。
  • 题目条件等价于联通块里的最大边权小于等于 \(w_i\) ,对于最大边权小于等于 \(w_i\) ,完全等价于最小瓶颈生成树上路径边权最大值的限制
  • //贪心的考虑走最小瓶颈生成树上的边这样可以使边权尽量小
  • 考虑\(kruskal\)重构树,一个节点能到达的边权小于等于 \(w_i\) 的点在重构树上是一个子树。子树的根的点权 \(<=w_i\)
  • 问题就转化成求子树内点权第 \(k_i\) 小的点
  • 这个问题可以用主席树轻松解决,但是我不会

小结

  • 记住可以用kruskal重构树做。暴力求子树点权第 \(k\)

归程

题意

给出一张N个点M条边的图。每条边有两个值:海拔和距离。
接下来Q组询问,每次给出水位线和起点。求
从起点出发到1号节点的最小步行距离。 从起点出发时可以驾车,驾车不算做步行距离。但车不能经过海拔低于水位线的边,在那之前需要弃车。
简单来说,就是对于起点,找到边权最小值大于等于 \(w\) 的边集中到1号点的最短路

思路

  • 找从一个点到可以到达的点集,并边权满足不等式。
  • 这问题在上题已经解决了
  • 我们以海拔建最大瓶颈生成树。
  • 以海拔建重构树,以每个点到1号点的最短路为重构树叶子节点上的点权
  • 用倍增求出子树根,在这个子树中求点权最小的点。
  • 在子树中求最小的点权可以dfs一下

Bug

  • 跑最短路的图是原图,不能在重构树上跑,所以要建两次图。
  • 建最大瓶颈生成树的点要2倍,边是点的4倍
  • 多测,要清零,尤其是存边的变量tot和tit
  • 倍增往上跳的时候找到大于水位线的最高祖先

代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<vector>
#include<cstring>
#define M 400005
#define N 200005
#define ll long long
using namespace std;
typedef pair<int,int> pll;
int T,n,m,Q,K,S,tot,res,tit;
int head[M],tou[M],a[M],fa[M],dep[M],f[M][20],dis[M];
struct node{
	int from,to,high,net,len;
}mp[M],e[M*4],tu[M<<1];
void add(int x,int y){
	e[++tot].to=y,e[tot].net =head[x],head[x]=tot;
	e[++tot].to=x,e[tot].net =head[y],head[y]=tot;
}
void add2(int x,int y,int z){
	tu[++tit].to=x,tu[tit].len =z,tu[tit].net =tou[y],tou[y]=tit;
	tu[++tit].to=y,tu[tit].len =z,tu[tit].net =tou[x],tou[x]=tit;
}
bool cmp(node a,node b){
	return a.high >b.high ;
}
int find(int x){
	if(x==fa[x]) return x;
	return fa[x]=find(fa[x]);
}
void kruskal(){
	res=n;
	for(int i=1;i<2*n;i++) fa[i]=i;
	for(int i=1;i<=m;i++){
		int x=mp[i].from,y=mp[i].to ,z=mp[i].high ;
		int fx=find(x),fy=find(y);
		if(fx==fy) continue;
		++res;
		add(fx,res);
		add(fy,res);
		fa[fx]=res;
		fa[fy]=res;
		a[res]=z;
		f[fx][0]=res;
		f[fy][0]=res;
		if(res==2*n-1) break; 
	}
}
void spfa(){
	priority_queue<pll,vector<pll>,greater<pll> > q;
	for(int i=1;i<=n;i++) dis[i]=1e9+7;
	q.push(make_pair(0,1));
	dis[1]=0;
	while(!q.empty()){
		int u=q.top().second;
		if(dis[u]!=q.top().first){
			q.pop() ;
			continue;
		}
		q.pop() ;
		for(int i=tou[u];i;i=tu[i].net){
			int to=tu[i].to ;
			if(dis[to]>dis[u]+tu[i].len){
				 dis[to]=dis[u]+tu[i].len ;
				q.push(make_pair(dis[to],to)); 
			}
		}
	} 
}
void dfs(int x,int last){
	for(int i=head[x];i;i=e[i].net){
		int to=e[i].to ;
		if(to==last) continue;
		dep[to]=dep[x]+1;
		f[to][0]=x;
		dfs(to,x);
		dis[x]=min(dis[x],dis[to]);
	}
}
void cler(){
	tot=0;tit=0;//bug
	memset(tou,0,sizeof(tou));
	memset(head,0,sizeof(head));
	memset(mp,0,sizeof(mp));
	memset(f,0,sizeof(f));
	memset(dep,0,sizeof(dep));
}
inline int read(){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int main()
{
	T=read();
	while(T--){
		cler();
		n=read();m=read();
		for(int i=1;i<=m;++i){
			int u,v,l,a;
			u=read();v=read();l=read();a=read();
			add2(u,v,l);
			mp[i].from =u,mp[i].to =v,mp[i].high =a,mp[i].len =l;
		}
		sort(mp+1,mp+m+1,cmp);
		kruskal();
		spfa();
		for(int i=n+1;i<=res;i++) dis[i]=1e9+7; 
		dep[res]=1;
		dfs(res,0);
		for(int j=1;j<=19;j++)
		   for(int i=1;i<=res;i++)f[i][j]=f[f[i][j-1]][j-1];
        Q=read();K=read();S=read();
		int lastans=0;
		while(Q--){
			int v,p;
			v=read();p=read();
			v=(v+K*lastans-1)%n+1;
			p=(p+K*lastans)%(S+1);
			for(int i=19;i>=0;i--)
			  if(dep[v]-(1<<i)>0&&a[f[v][i]]>p) v=f[v][i]; 
			lastans=dis[v];
			printf("%d\n",lastans);
		} 
	}
	return 0;
} 

HNOI2006旅行

题目传送

link

思路

  • 求路径上的最大边比最小边最小。
  • 枚举最小边,将大于它的边一条一条的往里加,直到其联通,当然边从小到大排序。联通时的边一定是以当前边为最小边的答案。
  • 一个疑问是这样做的前提是可以重复走边

小结

  • 枚举最小边,比它大的往里加

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#define N 505
using  namespace std;
int n,up,down,m,s,t;
int fa[N];
bool first;
struct node{
	int from,to,val;
}e[5005];
inline int read(){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
	return s*w;
}
bool cmp(node a ,node b){
	return a.val <b.val;
}
int find(int x){
	if(x==fa[x]) return fa[x];
	return fa[x]=find(fa[x]);
}
int gcd(int x,int y){
	if(y==0) return x;
	return gcd(y,x%y);
}
void work(){
	sort(e+1,e+m+1,cmp);
	for(int i=1;i<=m;i++){
//		int K=0;
		for(int j=1;j<=n;j++) fa[j]=j;
		for(int k=i;k<=m;k++){
			int u=e[k].from ,v=e[k].to ;
		    int fu=find(u),fv=find(v);
		    if(fu==fv) continue;
		    fa[fu]=fv;
		    if(find(s)==find(t)){
		    	if(first==0) first=1,up=e[k].val,down=e[i].val;
	           	if(up*e[i].val>down*e[k].val){
		          	up=e[k].val ;
		          	down=e[i].val;
		        }
		    	break;
			}
		}
//		cout<<find(s)<<" "<<find(t)<<endl;
//		if(find(s)!=find(t)) {
//			printf("IMPOSSIBLE\n");
//			continue;
//		}
//		cout<<e[i].val<<" "<<e[K].val<<endl;
		
	}
}
int main(){
	n=read();m=read();
	for(int i=1;i<=m;i++) e[i].from =read(),e[i].to=read(),e[i].val =read();
	s=read();t=read();
	work();
//	cout<<up<<" "<<down<<endl;
	if(first==0) {
		printf("IMPOSSIBLE\n");
		return 0;
	}
	int gd=gcd(up,down);
	up/=gd;
	down/=gd;
	if(down==1) printf("%d",up);
	else printf("%d/%d",up,down);
	return 0;
}
posted @ 2019-10-10 16:20  _Vimin  阅读(363)  评论(0编辑  收藏  举报