/*************************************************演算法引入:最小k度限制產生樹,就是指有特殊的某一點的度不能超過k時的最小產生樹;如果T是G的一個產生樹且dT(v0)=k,則稱T為G的k度限制產生樹;G中權值和最小的k度限制產生樹稱為G的最小k度產生樹;演算法思想:設特殊的那點為v0,先把v0刪除,求出剩下連通圖的所有最小產生樹;假如有m棵最小產生樹,那麼這些產生樹必定要跟v0點相連;也就是說這棵產生樹的v0點至少是m度的;若m>k,條件不成立,無法找到最小k度限制產生樹;若m<=k,則枚舉m到k的所有最小產生樹,即一步步將v0點的度加1,直到v0點的度為k為止;則v0點度從m到k的(k-m+1)棵最小產生樹中最小的那棵即為答案;演算法步驟:(1)先求出最小m度限制產生樹:原圖中去掉和V0相連的所有邊(可以先存兩個圖,建議一個鄰接矩陣,一個鄰接表,用方便枚舉邊的鄰接表來構造新圖);得到m個連通分量,則這m個連通分量必須通過v0來串連;則在圖G的所有產生樹中dT(v0)>=m;則當k<m時,問題無解;對每個連通分量求一次最小產生樹;對於每個連通分量V’,用一條與V0直接連接的最小的邊把它與V0點串連起來,使其整體成為一個產生樹;就得到了一個m度限制產生樹,即為最小m度限制產生樹;(2)由最小m度限制產生樹得到最小m+1度限制產生樹;串連和V0相鄰的點v,則可以知道一定會有一個環出現(因為原來是一個產生樹);只要找到這個環上的最大權邊(不能與v0點直接相連)並刪除,就可以得到一個m+1度限制產生樹;枚舉所有和V0相鄰點v,找到替換後,增加權值最小的一次替換(如果找不到這樣的邊,就說明已經求出);就可以求得m+1度限制產生樹;如果每添加一條邊,都需要對環上的邊一一枚舉,時間複雜度將比較高;用動態規劃解決;設dp(v)為路徑v0—v上與v0無關聯且權值最大的邊;定義father(v)為v的父結點,由此可以得到狀態轉移方程:dp(v)=max(dp(father(v)),ω(father(v),v));邊界條件為dp[v0]=-∞(因為每次尋找的是最大邊,所以-∞不會被考慮),dp[v’]=-∞|(v0,v’)∈E(T);(3)當dT(v0)=k時停止(即當V0的度為k的時候停止),但不一定k的時候最優;演算法實現:並查集+kruskal;首先,每個連通分量的的最小產生樹可以直接用一個迴圈,迴圈著Kruskal求出;這裡利用了聯通分量間的獨立性,對每個連通分量分別求最小產生樹,和放在一起求,毫不影響;而且kruskral演算法保證了各連通分量邊的有序性;找最小邊的時候,可以用動態規劃,也可以這麼做:先走一個迴圈,但我們需要逆過來加邊,將與v0關聯的所有邊從小到達排序;然後將各連通分量串連起來,利用並查集可以保證每個連通分量只有一條邊與v0相連;由於邊已經從小到達排序,故與每個連通分量相連的邊就是每個連通分量與v0相連中的最小邊;然後求m+1度的最小產生樹時,可以直接用DFS,最小產生樹要一直求到k度,然後從中找出一個最優值;演算法測試:PKU1639(Picnic Planning);題目大意:給出m條邊,每條邊有兩個端點和一個權值;求這個圖在滿足以下條件的情況下的最小產生樹;在所有點中,有一個特殊點Park,它在求得的最小產生樹中的度必須小於等於某個值;**************************************************/#include<iostream>#include<string>#include<cstdio>#include<map>#include<cstring>#include<algorithm>using namespace std;const int INF=99999999;const int N=100;int n,m;//n為邊的數量,m表示限度值int cnt;//計算出來的結點數int set[N];bool flag[N][N];int G[N][N];int ans;map<string,int> Map;struct node{ int x,y,v;} a[N*N];struct edge{ int x,y,v;} dp[N];int get_num(string s)//返回每個人對應結點{ if(Map.find(s)==Map.end())//沒有搜尋到該索引值 { Map[s]=++cnt;//對應建圖 } // cout<<" Map["<<s<<"]=="<<Map[s]<<endl; return Map[s];}bool cmp(node a,node b){ return a.v<b.v;}int find_set(int x){ if(x!=set[x]) set[x]=find_set(set[x]); return set[x];}inline void union_set(int x,int y){ set[y]=x;}void kruskal()//求m個連通分量的最小產生樹{ for(int i=1; i<=n; i++) { if(a[i].x==1||a[i].y==1) continue; int x=find_set(a[i].x); int y=find_set(a[i].y); if(x==y) continue; flag[a[i].x][a[i].y]=flag[a[i].y][a[i].x]=true; set[y]=x; ans+=a[i].v; }}void dfs(int x,int fa){ for(int i=2; i<=cnt; i++) if(i!=fa&&flag[x][i]) { if(dp[i].v==-1) { if(dp[x].v>G[x][i])//dp(v)=max(dp(father(v)),ω(father(v),v)); { dp[i]=dp[x]; } else { dp[i].v=G[x][i]; dp[i].x=x; dp[i].y=i; } } dfs(i,x); }}void init(){ ans=0; cnt=1; Map["Park"]=1; memset(flag,0,sizeof(flag)); memset(G,-1,sizeof(G)); scanf("%d",&n); for(int i=1; i<N; i++)//並查集初始化 set[i]=i; string s; for(int i=1; i<=n; i++) { cin>>s; a[i].x=get_num(s); cin>>s; a[i].y=get_num(s); cin>>a[i].v; if(G[a[i].x][a[i].y]==-1) G[a[i].x][a[i].y]=G[a[i].y][a[i].x]=a[i].v; else//有重邊 G[a[i].x][a[i].y]=G[a[i].y][a[i].x]=min(G[a[i].y][a[i].x],a[i].v); } scanf("%d",&m);//m表示限度值}void solve(){ int tmp[N],Min[N]; for(int i=1; i<=cnt; i++) Min[i]=INF; sort(a+1,a+1+n,cmp); kruskal(); for(int i=2; i<=cnt; i++) { if(G[1][i]!=-1) { int t=find_set(i); if(Min[t]>G[1][i])//求每個連通分量中和頂點1串連的最小權邊 { tmp[t]=i; Min[t]=G[1][i]; } } } int t=0;//t表示最小限度 for(int i=1; i<=cnt; i++) if(Min[i]!=INF) { t++; flag[1][tmp[i]]=flag[tmp[i]][1]=true; ans+=G[1][tmp[i]]; } for(int i=t+1; i<=m; i++)//枚舉t到m的所有最小產生樹,即一步步將v1點的度加1,直到v1點的度為m為止; { memset(dp,-1,sizeof(dp));//dp[v]為路徑v0—v上與v0無關聯且權值最大的邊; dp[1].v=-INF; for(int j=2; j<=cnt; j++) if(flag[1][j]) dp[j].v=-INF; dfs(1,-1); int tmp,Min=INF; for(int j=2; j<=cnt; j++) if(G[1][j]!=-1) { if(Min>G[1][j]-dp[j].v) { Min=G[1][j]-dp[j].v; tmp=j; } } if(Min>=0)//找不到這樣的邊,就說明已經求出 break; flag[1][tmp]=flag[tmp][1]=true; int x=dp[tmp].x; int y=dp[tmp].y; flag[x][y]=false; flag[y][x]=false; ans+=Min; } printf("Total miles driven: %d\n",ans);}int main(){ freopen("C:\\Users\\Administrator\\Desktop\\kd.txt","r",stdin); init(); solve(); return 0;}