求最小生成树是否唯一(求解次小生成树)例题poj1679

思路:
prim算法:思路1:

思路2:

算法过程:
1.先用prim算法求出最小生成树。并在其过程中保存加入到MST中的Max[i][j]( i 到 j 路径中的最大边 )
2.对未加入MST的边 i <->j 进行遍历:从MST中去掉i j路径中的最大边,加入G[i][j],如果得到的生成树的权值和MST的相等,则存在次小生成树的权值=MST的权值和

Poj1679
Code:


#include <iostream>
#include <cstdio>
#include <string.h>
#define INF 0x3f3f3f3f
using namespace std;
const int AX = 1e3+66;
int G[AX][AX];
int vis[AX];
int dis[AX];
int n , m; 
int pre[AX];
int Max[AX][AX];
int mark[AX][AX];
int ibest  ;
int prim( int v0 ){
	vis[v0] = 1;
	int ans = 0;
	for( int i = 1; i <= n ; i++ ){
		dis[i] = G[i][v0];
		pre[i] = v0;
	}
	dis[v0] = 0;
	int u = v0;
	for( int i = 1 ; i <= n ; i++ ){
		int minus = INF;
		for( int j = 1 ; j <= n ; j++ ){
			if( !vis[j] && dis[j] < minus ){
				u = j;
				minus = dis[j];
			}
		}
		if( minus == INF ) { break;}
		vis[u] = 1;
		mark[u][pre[u]] = mark[pre[u]][u] = 1;
		ans += minus ;
		for( int j = 1 ; j <= n ; j++ ){
			if( vis[j] ){ Max[u][j] = Max[j][u] = max( Max[pre[u]][j] , dis[u] ) ;}
			if( !vis[j] && dis[j] > G[u][j] ){
				dis[j] = G[u][j];
				pre[j] = u;
			}
		}
	}
	return ans ;
}

int SMST(){
	int minus = INF;
	for(int i = 1 ;  i <= n ; i++ ){
		for( int j = 1 ; j <= n ; j++ ){
			if( i != j && !mark[i][j] ){
				minus = min( minus , ibest + G[i][j] - Max[i][j] );
			}
		}
	}
	if( minus == INF ) return -1;
	return minus;
}

int main(){
	int T;
	int x, y , w;
	scanf("%d",&T);
	while( T-- ){		
		memset( vis, 0 ,sizeof(vis) );
		memset( Max, 0 ,sizeof(Max) );
		memset( pre, 0 ,sizeof(pre) );
		memset( dis, 0 ,sizeof(dis) );
		memset( mark, 0 ,sizeof(mark) );
		scanf("%d%d",&n,&m);
		for( int i = 1 ;  i <= n ; i++ ){
			for( int j = 1 ; j <= n ; j++ ){
				if( i == j ) G[i][j] = 0;
				else G[i][j] = INF;
			}
		}
		for( int i = 0 ; i < m ; i++ ){
			scanf("%d%d%d",&x,&y,&w);
			G[x][y] = G[y][x] = w;
		}
		ibest = prim(1);
		int tmp = SMST();
		if( tmp == ibest ){
			printf("Not Unique!\n");
		}else{
			printf("%d\n",ibest);
		}
	}
	return 0 ;
}

时间更优解法

上述解法在枚举不在生成树中的两点时复杂度是O(n*n)
下面的解法只需要询问所有不在生成树中的边数即可,主要复杂度是Tarjan算法,O(n)

利用Tarjan+Kruskal算法。先求出最小生成树,然后利用Tarjan算法维护生成树中任意两点路径中的最长边,将不在最小生成树中的边<x,y> 都当做询问生成树中<x,y>路径中的最长边,用最小生成树边的总和ans - 最长边 + 询问边<x,y>长度,每次都取最小值。
最后判断上述最小值与ans关系。

#include <iostream>
#include <cstdio>
#include <vector>
#include <string.h>
#include <algorithm>
#define LL long long
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std ;
typedef pair<int,int>P;
const int AX = 2e3 + 666 ;
struct Edge {
	int u , v ;
	LL w ;
	Edge() {}
	Edge( int u , int v , LL w ):u(u),v(v),w(w) {}
	bool operator < ( const Edge &a )const {
		return w < a.w ;
	}
};
struct Node {
	int u , v , nxt ;
	LL w ;
	Node() {}
	Node( int u , int v , LL w , int nxt ):u(u),v(v),w(w),nxt(nxt) {}
} G[AX<<1] ;

struct RES {
	int x , y , id ;
	RES() {}
	RES( int x , int y , int id ):x(x),y(y),id(id) {}
};
vector<Edge>E ;
int mark[AX] ;
LL maxn[AX] ;
LL ans ;
int n , m ;
int head[AX] ;
int vis[AX] ;
int pre[AX] ;
int tot ;
LL res ;
int find( int x ) {
	return x == pre[x] ? x : pre[x] = find(pre[x]) ;
}
void mix( int x , int y ) {
	x = find(x) ;
	y = find(y) ;
	if( x != y ) {
		pre[x] = y ;
	}
}

int find_t( int x ) {
	int tmp = pre[x] ;
	if( x != tmp ) {
		pre[x] = find(pre[x]);
	}
	maxn[x] = max( maxn[x] , maxn[tmp] );
	return pre[x] ;
}
void add( int u , int v , LL w ) {
	G[tot] = Node( u , v , w , head[u] ) ;
	head[u] = tot++ ;
	G[tot] = Node( v , u , w , head[v] ) ;
	head[v] = tot++ ;
}
void Kruskal() {
	sort( E.begin() , E.end() ) ;
	for( int i = 0 ; i < m ; i++ ) {
		int u = find( E[i].u ) ;
		int v = find( E[i].v ) ;
		if( u != v ) {
			mix( u , v ) ;
			ans += E[i].w ;
			mark[i] = 1 ;
		}
	}
}
vector<RES>tmp[AX] ;
vector<P>que[AX] ;
void Tarjan( int u ) {
	vis[u] = 1 ;
	for( int i = head[u] ; ~i ; i = G[i].nxt ) {
		int v = G[i].v ;
		if( vis[v] ) continue ;
		Tarjan(v) ;
		pre[v] = u ;
		maxn[v] = G[i].w ;
	}
	for( int i = 0 ; i < (int)que[u].size() ; i++ ) {
		int v = que[u][i].first ;
		int id = que[u][i].second ;
		tmp[find_t(v)].push_back(RES(u,v,id));
	}
	for( int i = 0 ; i < (int)tmp[u].size() ; i++ ) {
		int x = tmp[u][i].x ;
		int y = tmp[u][i].y ;
		int id = tmp[u][i].id ;
		find_t(x) ;
		find_t(y) ;
		LL temp = max( maxn[x] , maxn[y] ) ;
		LL t = ans + E[id].w - temp ;
		res = min( res , t ) ;
	}
}

int main() {
	int T ;
	scanf("%d",&T);
	while( T-- ) {
		scanf("%d%d",&n,&m);
		memset( mark , 0 , sizeof(mark) ) ;
		memset( head , -1 , sizeof(head) ) ;
		memset( vis , 0 , sizeof(vis) ) ;
		memset( maxn , 0 , sizeof(maxn) ) ;
		E.clear() ; 
		res = INF ;
		int x , y ;
		LL w ;
		tot = 0 ;
		for( int i = 0 ; i < m ; i++ ) {
			scanf("%d%d%lld",&x,&y,&w) ;
			E.push_back( Edge( x , y , w ) ) ;
		}
		ans = 0 ;
		for( int i = 1 ; i <= n ; i++ ) {
			pre[i] = i ;
			que[i].clear() ; 
		}
		Kruskal();
		for( int i = 0 ; i < m ; i++ ) {
			if( !mark[i] ) {
				que[E[i].u].push_back(P(E[i].v,i)) ;
				que[E[i].v].push_back(P(E[i].u,i)) ;
			}else add( E[i].u , E[i].v , E[i].w ) ;
		}
		for( int i = 1 ; i <= n ; i++ ) {
			pre[i] = i ;
		}
		Tarjan(1);
		if( res == ans ){
			printf("Not Unique!\n");
		}else{
			printf("%lld\n",ans);
		}
	}
	return 0 ;
}

例2:

这里写图片描述

思路:这道题会出现重边,用Kruscal算法处理起来比较方便。(用邻接表方便,邻接矩阵还要考虑同两点之间的多条权值相同的边。)
cnt1记录的是可以加入集合的边数,cnt2记录的是选中一条加入到集合,如果可选的大于构成最小生成树所需要的,那么i就可以构成不唯一的最小生成树。
Code:

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int AX = 2e5+666;
struct Node{
	int u , v ;
	LL w;
}G[AX];
int n , m;
int pre[2005];
int find( int x ){
	return x == pre[x] ? pre[x] : pre[x] = find(pre[x]);
}

bool cmp( const Node &a , const Node &b ){
	return a.w < b.w;
}

void kruscal(){
	int cnt1 = 0;
	int cnt2 = 0;
	for( int i = 1 ; i <= n ; i++ ){
		pre[i] = i;
	}
	sort( G,  G + m , cmp );
	for( int i = 0 ; i < m ; ){
		int j = i;
		while( j < m && G[j].w == G[i].w ){
			int u = find( G[j].u );
			int v = find( G[j].v );
			if( u != v ){
				cnt1 ++;
			}
			j++;
		}
		j = i;
		while( j < m && G[j].w == G[i].w ){
			int u = find( G[j].u );
			int v = find( G[j].v );
			if( u != v ){
				cnt2 ++;
				pre[u] = v ;
			}
			j++;
		}
		i = j;
		if( cnt2 == n - 1 ) break;
	}
	if( cnt2 < n - 1 || cnt1 > cnt2 ){
		printf("zin\n");
	}else{
		printf("ogisosetsuna\n");
	}
}

int main(){
	int T;
	int x, y ;
	LL w;	
	scanf("%d%d",&n,&m);
	for( int i = 0 ; i < m ; i++ ){
		scanf("%d%d%lld",&x,&y,&w);
		G[i].u = x;
		G[i].v = y;
		G[i].w = w;
	}
	kruscal();
	return 0 ;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值