題意:
給一個無向圖..判斷這個圖中的每個邊...any 為其是該圖所有最小產生樹共有的邊...at least one..該邊至少為一個最小產生樹的邊..none..該邊不存在該圖的任何的最小產生樹中..
題解:
那麼思考如何判斷一個邊是某個最小產生樹的邊...回顧kruskal演算法..將所有的邊按其長度從小到大排好序..從短邊開始連點...連點的同時縮點...用並查集維護...本題本問的原理類似...只是在加邊連點的時候一次性將長度相等的且之前兩端點不在一個集合的同時做.一起加..一起連..一起縮點....可以想到..如果有幾條長度相同的當前可用最小邊能夠加入現有的圖中...由於這幾條邊都是當前最小..按照傳統的kruskal..這幾條邊都有可以選入最小產生樹的...所以這幾條邊至少應該是at
least one...並連點縮點...而後面有較長的邊..兩端點已經在一個集合..其必定不是最小產生樹的邊...
第二個問題..如何判斷一條邊是所有最小產生樹都有的...這裡利用tarjan演算法求無向圖的橋...做kruskal時..每輪加相同長度的邊連點後(縮點前)..對現有的無向圖跑一次tarjan..求出橋..就是any....而tarjan求無向圖的橋思路也很簡單..從任意點開始dfs..形成一顆搜尋樹.dfn代表遍曆每個點的順序標號..low代表這個點可以通過其子節點最上可以返回到哪個位置...若不能回到該點或者上方..那麼對應的該邊必定是橋...也就是對於一條邊(s,e)..若low(e)<dfn(s)...則這條邊是橋..在一個注意的是由於本題是存在重邊的..如果直接判斷不能回到其父親會出錯..所以要改為不沿原路走回去...
最後回到問題的本質...如果一個邊是at least one..說明其在做kruskal時..至少有一個和其長度相同的邊兩端連了與其兩端相同的集合...如果一個邊是any..說明其在做kruskal時..與其長度相同的邊只有它自己一個兩端連了這兩個集合... 第一個範例就很好的說明了這個意思...
本題的基本思路繞了很多..說白了..和kruskal沒區別..可以想象..若每條邊長度都不同..找at least one就是裸的kruskal...如果有多個相同的邊..要一起加的原因是有可能兩個長度相同的邊串連了同兩個集合..如果直接kruskal就只會找出條其中一條邊了...但根據題意這樣這兩條邊都是at least one的..每次處理完一組長度相同的邊..留下的就是一堆點的集合...當前的點集狀態和做裸的kruskal做到當前邊的狀態是完全相同的...後面的邊在這個基礎上再處理..
Program:
#include<iostream>#include<stdio.h>#include<string.h>#include<cmath>#include<queue>#include<stack>#include<set>#include<algorithm>#define ll long long#define oo 1000000007#define pi acos(-1.0)#define MAXN 100005 using namespace std; struct node{ int x,y,l,id,next; bool operator <(node a) const { return l<a.l; }}line[2*MAXN],Dline[MAXN];int n,m,_next[MAXN],father[MAXN],ans[MAXN],dfn[MAXN],low[MAXN],_DfsIndex; void addline(int x,int y,int l,int id,int m){ line[m].next=_next[x],_next[x]=m; line[m].x=x,line[m].y=y,line[m].l=l,line[m].id=id; }int getfather(int x){ if (father[x]==x) return x; return father[x]=getfather(father[x]);} void tarjan(int x,int f) { int k,y; dfn[x]=low[x]=++_DfsIndex; for(k=_next[x];k;k=line[k].next) if(line[k].id!=f) //id標記的路..不走原路回去 { y=line[k].y; if(!dfn[y]) { tarjan(y,line[k].id); low[x]=min(low[x],low[y]); if (low[y]>dfn[x]) ans[line[k].id]=2; //是橋.是any }else low[x]=min(low[x],dfn[y]); } return; } void Kruskal(){ int i,x,s,e,LineNum=0; sort(Dline+1,Dline+1+m); //邊按長度排序 memset(dfn,0,sizeof(dfn)); memset(ans,0,sizeof(ans)); memset(_next,0,sizeof(_next)); for (i=1;i<=n;i++) father[i]=i; _DfsIndex=0; x=1; while (x<=m) { s=x; int fx,fy; for (e=s;e<=m;e++) if (Dline[e].l!=Dline[s].l) break; //找出一組相同的..s為頭..e為尾 e--; for (i=s;i<=e;i++) { fx=getfather(Dline[i].x),fy=getfather(Dline[i].y); if (fx==fy) continue; ans[Dline[i].id]=1; //肯定是在某個最小產生樹中了 addline(fx,fy,Dline[i].l,Dline[i].id,++LineNum); addline(fy,fx,Dline[i].l,Dline[i].id,++LineNum); //加邊 } for (i=s;i<=e;i++) { fx=getfather(Dline[i].x),fy=getfather(Dline[i].y); if (fx==fy) continue; tarjan(fx,0); father[fx]=fy; //縮點 } LineNum=0; for (i=s;i<=e;i++) { fx=getfather(Dline[i].x),fy=getfather(Dline[i].y); dfn[fx]=dfn[fy]=_next[fx]=_next[fy]=0; //去邊 } x=e+1; }}int main(){ int i; while (~scanf("%d%d",&n,&m)) { for (i=1;i<=m;i++) { int x,y,l; scanf("%d%d%d",&x,&y,&l); Dline[i].x=x,Dline[i].y=y,l,Dline[i].l=l,Dline[i].id=i; } Kruskal(); for (i=1;i<=m;i++) if (!ans[i]) printf("none\n"); else if (ans[i]==1) printf("at least one\n"); else printf("any\n"); } return 0;}