思路:
prim算法:
算法过程:
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 ;
}