【NOIP 模拟题】最小生成树(Tarjan求桥)

45 篇文章 0 订阅
35 篇文章 0 订阅

                                                                                                                                

                                   

                                  【输出样例2】

              

—————————————————————————————————————————————————

【题解】【Tarjan缩点】

【首先通过Kruskal的思想来考虑,从边权小的开始,把不在同一并查集的点并查到一起】

【这样看来,如果当前图的边权都不同,那么最小生成树是唯一确定的,即要么是"any"要么是"none";那么我们需要考虑的就是有相同边权的图】

【按照kruskal的思想,先将边按边权排序,将边权相同的边放到一起处理,将不在同一个并查集里的两边,并查到一起,并用这些边建新图,并用Tarjan求桥,如果一条边是桥,那么就是"any",否则就是"at least one"】

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct data{
	int x,y,val;
	int num;
}g[100010];//记录原始图 
int f[100010],nm[100010],dfn[100010],low[100010];//f数组维护并查集;nm表示当前边在最小生成树中出现的可能性是怎样的; 
int a[200010],nxt[200010],p[100010],w[200010],tot;//next数组存图 
int n,m,cnt;
int tmp(data a,data b)
{
	return a.val<b.val;
}
inline void add(int x,int y,int v)
{
	tot++; a[tot]=y; nxt[tot]=p[x]; p[x]=tot; w[tot]=v;
	tot++; a[tot]=x; nxt[tot]=p[y]; p[y]=tot; w[tot]=v;
}
int find(int x)//并查集 
{
	if(f[x]==x) return x;
	f[x]=find(f[x]);
	return f[x];
}
void tarjan(int x,int fa)
{
	dfn[x]=low[x]=++tot;
	int u=p[x];
	while(u)
	 {
	 	if(a[u]==fa) {fa=0; u=nxt[u]; continue;}
	 	if(!dfn[a[u]]) 
	 	 {
	 	 	tarjan(a[u],x);
	 	 	low[x]=min(low[a[u]],low[x]);
	 	 	if(low[a[u]]>dfn[x]) nm[w[u]]=2;
		  }
		 else low[x]=min(dfn[a[u]],low[x]);
	 	u=nxt[u];
	 }
}//tarjan求桥 
int main()
{
	freopen("mst.in","r",stdin);
	freopen("mst.out","w",stdout);
	int i,j;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;++i) f[i]=i;
	for(i=1;i<=m;++i) scanf("%d%d%d",&g[i].x,&g[i].y,&g[i].val),g[i].num=i;
	sort(g+1,g+m+1,tmp);
	i=1;cnt=0;
	while(i<=m)
	 {
	 	j=i;
	 	while(g[i].val==g[j].val) ++j;//找出边权相同的区间 
	 	--j; tot=0;
	 	for(int k=i;k<=j;++k)
	 	 {
	 	 	int f1=find(g[k].x),f2=find(g[k].y);
	 	 	if(f1==f2)  continue;
	 	 	nm[g[k].num]=1; dfn[f1]=dfn[f2]=0; p[f1]=p[f2]=0;
		  }//处理当期区间前重置、预处理 
	 	for(int k=i;k<=j;++k)
	 	 if(nm[g[k].num]==1)
	 	  {
	 	  	int f1=find(g[k].x),f2=find(g[k].y);
	 	  	add(f1,f2,g[k].num);
		   }//把当前情况下会加入最小生成树的边加入图中 
		for(int k=i;k<=j;++k)
		 if(!dfn[find(g[k].x)]) tarjan(g[k].x,0);//Tarjan求桥 
		for(int k=i;k<=j;++k)
		 {
		 	int f1=find(g[k].x),f2=find(g[k].y);
		 	if(f1!=f2) f[f1]=f2,cnt++;
		 }//把当前图中有的边,即联通两个联通块的边并查到一起 
		if(cnt==n-1) break;
		i=j+1;
	 }
	for(i=1;i<=m;++i)
	 {
	 	if(!nm[i]) {printf("none\n"); continue;}
	    if(nm[i]==1) {printf("at least one\n"); continue;}
	    printf("any\n");
	 }
	return 0;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值