最小树形图(朱刘算法)
前天做题遇到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; }