前天做題遇到hdu2121 時一點思路都沒有,於是往后面一道題去了,結果做了hdu2122(求最小生成樹)之后受到啟發,hdu2121分明就是一道求有向圖的生成樹,也就是最小樹形圖。。。。
於是搜了很久,啃了很久,終於……
主要在這下面三個博客上學習的:
很贊的圖,http://hi.baidu.com/bin183/blog/item/45c37950ece4475f1138c273.html
很牛逼的理論http://www.zlinkin.com/?p=63
很好的模板+題目http://www.notonlysuccess.com/index.php/mst/#more-315
定根,求最小樹形圖,模板題

Source Code Problem: 3164 User: nanke_ Memory: 276K Time: 125MS Language: C++ Result: Accepted Source Code #include<iostream> #include<algorithm> #include<string> #include<math.h> using namespace std; const int N = 100+10; const int M = 10000+10; struct Point { double x,y; }p[N]; struct edge { int u,v; double cost; edge(){} edge(int u,int v,double c):u(u),v(v),cost(c){} }e[M]; int pre[N],hash1[N],vis[N]; double In[N]; inline double dist(Point& a,Point& b) { return sqrt(double((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y))); } double Directed_MST(int root,int n,int m) { double ret=0; while(true) { for(int i=0;i<n;i++) In[i]=INT_MAX; for(int i=0;i<m;i++)//找最小入邊 { int u=e[i].u; int v=e[i].v; if(e[i].cost<In[v] && u!=v){ pre[v]=u; In[v]=e[i].cost; } } for(int i=0;i<n;i++) { if(i==root) continue; if(In[i]==INT_MAX) return -1; } int cntnode=0; memset(hash1,-1,sizeof(hash1)); memset(vis,-1,sizeof(vis)); In[root]=0; for(int i=0;i<n;i++)//找環 { ret+=In[i]; int v=i; while(vis[v]!=i && hash1[v]==-1 && v!=root) { vis[v]=i; v=pre[v]; } if(v!=root && hash1[v]==-1) { for(int u=pre[v];u!=v;u=pre[u]) hash1[u]=cntnode; hash1[v]=cntnode++; } } if(cntnode==0) break; for(int i=0;i<n;i++) if(hash1[i]==-1) hash1[i]=cntnode++; for(int i=0;i<m;i++)//重標記 { int v=e[i].v; e[i].u=hash1[e[i].u]; e[i].v=hash1[e[i].v]; if(e[i].u!=e[i].v) e[i].cost-=In[v]; } n=cntnode; root=hash1[root]; } return ret; } int main() { int a,b; int n,m; while(scanf("%d %d",&n,&m)==2) { for(int i=0;i<n;i++) scanf("%lf %lf",&p[i].x,&p[i].y); int mm=0; for(int i=0;i<m;i++) { scanf("%d %d",&a,&b); if(a==b)continue; a--,b--; e[mm++]=edge(a,b,dist(p[a],p[b])); } double ans=Directed_MST(0,n,mm); if(ans==-1) puts("poor snoopy"); else printf("%.2f\n",ans); } return 0; }
不定根的題目,“只要虛擬一個根連所有的點的權為邊權總和+1,最后的結果減去(邊權+1)即可”。另外對於求最小根標號,只要搜索被選中的虛邊就可以判斷了。,與虛根相連的點即為原本的實根!
如果選擇了倆條或倆條以上的與虛根相連的邊,則不存在對應的最小樹形圖

#include<iostream> #include<climits> #include<math.h> using namespace std; const int N = 1000+10; const int M = N*N; struct edge { int u,v; int cost; }e[M]; int pre[N],id[N],visit[N];//id用來標記圈的 int in[N];//入弧最小的 int minRoot; int Directed_MST(int root,int n,int m)//n表示點數,m表示邊數,root表示根 { int u,v,i; int ret=0; while(true) { for(i=0;i<n;i++) in[i]=INT_MAX; for(i=0;i<m;i++) { u=e[i].u; v=e[i].v; if(e[i].cost<in[v]&&u!=v) { pre[v]=u;//找出每個點的最小入弧 if(u==root) minRoot=i; in[v]=e[i].cost; } } for(i=0;i<n;i++) { if(i==root) continue; if(in[i]==INT_MAX){//除根外有個節點無入弧,就返回false return -1; } } in[root]=0; int cnt=0; memset(id,-1,sizeof(id)); memset(visit,-1,sizeof(visit)); for(i=0;i<n;i++) { ret+=in[i];//進行縮圈 v=i; while(visit[v]!=i&&id[v]==-1&&v!=root) { visit[v]=i; v=pre[v]; } if(v!=root&&id[v]==-1) { for(u=pre[v];u!=v;u=pre[u]) id[u]=cnt; id[v]=cnt++; } } if(cnt==0) break; for(i=0;i<n;i++) { if(id[i]==-1) id[i]=cnt++; } for(i=0;i<m;i++) { v=e[i].v;//進行縮點,重新標記。 e[i].u=id[e[i].u]; e[i].v=id[e[i].v]; if(e[i].u!=e[i].v) e[i].cost-=in[v]; } n=cnt; root=id[root]; } return ret; } int main() { int n,m,m1; int T,c; int r=0; while(scanf("%d%d",&n,&m)!=EOF) { int i,a,b; r=0; m1=m; for(i=0;i<m;i++) { scanf("%d%d%d",&a,&b,&c); e[i].u=a; e[i].v=b; e[i].cost=c; r+=c; } r++; for(i=0;i<n;i++) { e[m].u=n; e[m].v=i; e[m].cost=r; m++; } int ans=Directed_MST(n,n+1,m); minRoot-=m1;//最小根對應的標號為i-m1 if(ans==-1||ans>=2*r) puts("impossible"); else printf("%d %d\n",ans-r,minRoot); puts(""); } return 0; }
這題目略有區別,卻更好理解。。
對於每一個household 有倆個選擇
1)自己挖井,花費 h*X
2)通過其他願意供水的household連一條水管,根據高度有倆種情況的花費
接下來,對應上面倆種選擇,我們先假定一個虛根,那么對應第一種選擇,我們可以通過虛根“供水”,虛根往每一個節點連一條邊,權值為h*x
對於第二種選擇,則將其與願意供水的節點連一條邊,權值為對應 的花費
這樣,就變成了定根的最小樹形圖,而且,一定有解,大不了就是每一個household都自己挖井

#include<iostream> #include<string> #include<algorithm> using namespace std; const int N = 1000+10; const int M = N*N; struct Point { int x,y,z; }p[N]; struct edge { int u,v,cost; edge(){} edge(int u,int v,int cost):u(u),v(v),cost(cost){} }e[M]; int pre[N],id[N],in[N],vis[N]; int X,Y,Z; inline int Directed_MST(int root,int n,int m)//n表示點數,m表示邊數,root表示根 { int u,v,i; int ret=0; while(true) { for(i=0;i<n;i++) in[i]=INT_MAX; for(i=0;i<m;i++) { u=e[i].u; v=e[i].v; if(e[i].cost<in[v]&&u!=v) { pre[v]=u;//找出每個點的最小入弧 in[v]=e[i].cost; } } for(i=0;i<n;i++) { if(i==root) continue; if(in[i]==INT_MAX){//除根外有個節點無入弧,就返回false return -1; } } in[root]=0; int cnt=0; memset(id,-1,sizeof(id)); memset(vis,-1,sizeof(vis)); for(i=0;i<n;i++) { ret+=in[i];//進行縮圈 v=i; while(vis[v]!=i&&id[v]==-1&&v!=root) { vis[v]=i; v=pre[v]; } if(v!=root&&id[v]==-1) { for(u=pre[v];u!=v;u=pre[u]) id[u]=cnt; id[v]=cnt++; } } if(cnt==0) break; for(i=0;i<n;i++) { if(id[i]==-1) id[i]=cnt++; } for(i=0;i<m;i++) { v=e[i].v;//進行縮點,重新標記。 e[i].u=id[e[i].u]; e[i].v=id[e[i].v]; if(e[i].u!=e[i].v) e[i].cost-=in[v]; } n=cnt; root=id[root]; } return ret; } inline int get_cost(Point& a,Point& b) { int dis=abs(a.x-b.x)+abs(a.y-b.y)+abs(a.z-b.z); if(a.z>=b.z) return dis*Y; return dis*Y+Z; } int main() { int n,m,k,a; while(scanf("%d %d %d %d",&n,&X,&Y,&Z)==4 && (n||X||Y||Z)) { m=0; for(int i=1;i<=n;i++) scanf("%d %d %d",&p[i].x,&p[i].y,&p[i].z); for(int i=1;i<=n;i++) { scanf("%d",&k); while(k--) { scanf("%d",&a); e[m++]=edge(i,a,get_cost(p[i],p[a])); } } for(int i=1;i<=n;i++) e[m++]=edge(0,i,p[i].z*X); printf("%d\n",Directed_MST(0,n+1,m)); } return 0; }