• 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
    ");
    	  else printf("YES
    ");
    	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
    ",l,mid,r,check(mid));
    //		cout<<ans<<" "<<check(ans)<<endl;
    	}
    	if(!check(ans)||(ton[0]!=n-1)){printf("-1
    ");return 0;}
    	printf("%d
    ",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
    ",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
    ",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
    ",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
    ");
    //			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
    ");
    		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;
    }
    
  • 相关阅读:
    js中==与===区别
    Initialization failure 0x0000000c
    Spring通过@Value注解注入属性的几种方式
    java中读取配置文件中数据的具体方法
    spring整合hibernate
    url上参数解析笔记
    编号的生成(日期+序列号)
    application.xml & -servlet.xml
    webApplicationContext 与servletContext
    Http协议整理
  • 原文地址:https://www.cnblogs.com/Vimin/p/11648812.html
Copyright © 2020-2023  润新知