修路方案
- 描述
南將軍率領着許多部隊,它們分別駐紮在N個不同的城市裏,這些城市分別編號1~N,由於交通不太便利,南將軍準備修路。
現在已經知道哪些城市之間可以修路,如果修路,花費是多少。
現在,軍師小工已經找到了一種修路的方案,能夠使各個城市都聯通起來,而且花費最少。
但是,南將軍說,這個修路方案所拼成的圖案很不吉利,想讓小工計算一下是否存在另外一種方案花費和剛纔的方案一樣,現在你來幫小工寫一個程序算一下吧。
- 輸入
- 第一行輸入一個整數T(1<T<20),表示測試數據的組數
每組測試數據的第一行是兩個整數V,E,(3<V<500,10<E<200000)分別表示城市的個數和城市之間路的條數。數據保證所有的城市都有路相連。
隨後的E行,每行有三個數字A B L,表示A號城市與B號城市之間修路花費爲L。 - 輸出
- 對於每組測試數據輸出Yes或No(如果存在兩種以上的最小花費方案則輸出Yes,如果最小花費的方案只有一種,則輸出No)
- 樣例輸入
2 3 3 1 2 1 2 3 2 3 1 3 4 4 1 2 2 2 3 2 3 4 2 4 1 2
- 樣例輸出
No
Yes
題目分析求次小生成樹一般有兩種辦法:
a.利用克魯斯卡爾先生成一次最小生成樹並保存每條樹上的邊的下標,然後遍歷這些樹上的邊,然後在把每次樹上的邊執行刪除-->重新建樹-->更新最小權值-->恢復的過程,每次刪除樹一條邊後,就破壞了原來的樹,重新建成的樹一定不是原來的樹,重新建成的樹的最小權值就是次小的,注意每次刪邊的時候如果刪掉的是橋從而導致圖的不聯通,所以在克魯斯卡爾中需要利用並查集統計圖中的聯通分量的個數,如果整個圖不聯通返回無窮大,使權值本次更新一定失敗。
複雜度O(n-1*優化後的克魯斯卡爾的複雜度)
#include<iostream>//導入輸入輸出流頭文件 #include<algorithm>//導入頭文件 using namespace std;//導入標準命名空間 typedef struct edge//定義邊的結構體 { int v,w,d;//定義一條邊的兩個端點及其邊長 bool is_delete;//定義是否被刪除標誌 } Edge; #define MAXN 200007//定義最大邊長 #define MAXNUM 0x3f3f3f3f//定義無窮大值 Edge bian[MAXN];//建立邊的數組 int pre[MAXN];//定義集合父節點數組 int n,e;//定義邊的數目和點的數目 int MST_id[MAXN];//定義樹的下標保存數組 int mid_i;//生成樹數組的下標 int cnt;//定義圖內連同分量的個數 bool cmp(Edge a,Edge b) { return a.d<b.d; } int Find(int x)//壓縮路徑 { int temp,p=x; while(x!=pre[x]) x=pre[x];//先找到x所屬集合的根節點 while(p!=x)//更新路徑上的根節點全部壓縮 { temp=pre[p];//從x到根節點路徑遍歷 pre[p]=x; p=temp; } return x;//返回該集合的根節點 } int kruskal(int p)//進入克魯斯卡爾算法 { int sum=0;//定義最小生成樹權值和sum for(int i=0;i<e;i++)//遍歷每條邊 { int p1=Find(bian[i].v); int p2=Find(bian[i].w); if(bian[i].is_delete==false&&p1!=p2)//如果這條邊沒有被標記刪除,並且兩個點集不一個父節點 { cnt--;//集合數目減一個 pre[p1]=pre[p2];//父節點改變 sum+=bian[i].d;//權值和加這條邊的權值 if(p==1)//如果是第一次生成 { MST_id[mid_i++]=i;//記錄最小生成樹的邊的id } if(cnt==1) break; } } if(cnt==1)//如果最後全圖聯通返回權值和 return sum; else//否則返回無窮大 return MAXN; } void init()//初始化父節點和集合個數cnt { for(int i=0;i<=n;i++) pre[i]=i; cnt=n; } int main() { ios::sync_with_stdio(false);//優化輸入輸出 int ncase;//定義測試組數 cin>>ncase; while(ncase--) { cin>>n>>e;//給定頂點數和邊數 mid_i=0; init();//初始化 for(int i=0; i<e; i++) { int v,w,d; cin>>v>>w>>d; bian[i].d=d; bian[i].v=v; bian[i].w=w;//建圖 bian[i].is_delete=false; } sort(bian,bian+e,cmp);//排序 int sum=kruskal(1);//獲得最小權值並標記最小生成樹的邊 int tsum=MAXNUM; for(int i=0;i<mid_i;i++) { //cout<<mid_i<<endl; init();//每次初始化 bian[MST_id[i]].is_delete=true;//把最小生成樹的一條邊刪除 tsum=min(kruskal(0),tsum);//更新最小值 bian[MST_id[i]].is_delete=false;//恢復這條邊 } if(tsum==sum)//如果數目大於0,就存在 { cout<<"Yes"<<endl; } else cout<<"No"<<endl; } }
第二種是藉助普里找不在樹中最大邊看看原圖中有沒有與之等價的,有就說明不唯一
O(n^2)
#include<iostream>//導入輸入輸出流函數 #include<string.h>//用到memset using namespace std;//導入標準命名空間 #define MAXN 505//定義最大的定點數 #define MAXNUM 0x3f3f3f3f//定義無窮大 int vis[MAXN];//訪問標記數組 int graph[MAXN][MAXN];//矩陣圖 int n,e;//頂點數,邊數 int low[MAXN];//保存已經建好的樹的頂點到各個點的最小值 int MST[MAXN][MAXN];//i到j路徑上的最小值 int is_MST[MAXN][MAXN];//是否在樹中 int pre[MAXN];//確定的那一點的邊上的另一個節點 int csum=MAXNUM; int sum=0; void init() { memset(graph,0x3f,sizeof(graph));//初始化圖 for(int i=0;i<=n;i++) graph[i][i]=0;//將自己和自己設置成0 memset(MST,0,sizeof(MST));//初始化爲0 memset(is_MST,0,sizeof(is_MST));//初始化爲0 csum=MAXNUM; sum=0; } bool prim(int s)//prim算法 { for(int i=1;i<=n;i++) { low[i]=graph[s][i];//每個點到s的距離 pre[i]=s;//每個點的直接前驅是s vis[i]=0;//初始化每個點都沒有被訪問過 } vis[s]=1;//把s點設置訪問過 for(int j=1;j<n;j++)//循環n-1次 { int tmin=MAXN;// int fa=0;// int ti=-1; for(int i=1;i<=n;i++) { if(vis[i]==0&&low[i]<tmin) { tmin=low[i]; ti=i; } }//找到最小的low以及下標 sum+=tmin; vis[ti]=1;//設置爲已經找到 fa=pre[ti];//取出ti這個點所在邊的另一個點 is_MST[ti][fa]=is_MST[fa][ti]=1;//將這條邊標記爲在樹中 for(int i=1;i<=n;i++) { if(vis[i]==1&&i!=ti) { MST[i][ti]=MST[ti][i]=max( MST[i][fa],low[ti]);//更新最大的從其他已經在樹中點到剛找的點的路徑上的最大邊 } if(!vis[i]&&low[i]>graph[ti][i])//如果沒有被訪問過,s鬆弛low { low[i]=graph[ti][i]; pre[i]=ti; } } } for(int i=1;i<=n;i++) { for(int j=1;j<i;j++) { if(is_MST[i][j]==0&&MST[i][j]!=MAXNUM)//如果有邊並且沒在樹裏面 { //res=min(res,mst-path[i][j]+g[i][j]); 求具體的csum csum=min(csum,sum-MST[i][j]+graph[i][j]);//求次小生成樹 if(graph[i][j]==MST[i][j])//如果在原圖中有等價的最大邊則一定存在 { //return true; } } } } return false; } int main() { ios::sync_with_stdio(false); int ncase; cin>>ncase; // ios::sync_with_stdio(false); while(ncase--) { cin>>n>>e; init(); for(int i=0;i<e;i++) { int v,w,d; cin>>v>>w>>d; graph[v][w]=d; graph[w][v]=d; // cout<<i<<endl; }//建圖 prim(1); if(sum==csum)//如果原來的和新建的一樣,那麼就輸出yes { cout<<"Yes"<<endl; } else { cout<<"No"<<endl; } } return 0; }
還是那句話點少用普里母
點多用克魯斯卡爾