最小树形图,就是给有向带权图中指定一个特殊的点root,求一棵以root为根的有向生成树T,并且T中所有边的总权值最小。也就是有向图的最小生成树。
1.找到除了root外的到每个点u权值最小的一条边,记为iw[u]。
2.如果存在除了root之外的孤立点,则不存在最小树形图。
3.如果有环,就把环缩成一个点,把所有环都缩点后对所有点重新编号。
4.更新其它点到环的距离,比如本来到还上v点的边,权值就减去iw[v]。
重复这个过程直到没有环。
第4步的原因是当前答案加上了这个环的权值,但是如果最后图里有到v的那条边,环上iw[v]这条边并不需要,可以删掉了,删掉也是生成树。所以把到v的边权值减去iw[v],最后加上这个权值也就相当于删掉了边iw[v]的权值。
网上的图
LRJ的邻接矩阵写法。
// 固定根的最小树型图,邻接矩阵写法 struct MDST { int n; int w[maxn][maxn]; // 边权 int vis[maxn]; // 访问标记,仅用来判断无解 int ans; // 计算答案 int removed[maxn]; // 每个点是否被删除 int cid[maxn]; // 所在圈编号 int pre[maxn]; // 最小入边的起点 int iw[maxn]; // 最小入边的权值 int max_cid; // 最大圈编号 void init(int n) { this->n = n; for(int i = 0; i < n; i++) for(int j = 0; j < n; j++) w[i][j] = INF; } void AddEdge(int u, int v, int cost) { w[u][v] = min(w[u][v], cost); // 重边取权最小的 } // 从s出发能到达多少个结点 int dfs(int s) { vis[s] = 1; int ans = 1; for(int i = 0; i < n; i++) if(!vis[i] && w[s][i] < INF) ans += dfs(i); return ans; } // 从u出发沿着pre指针找圈 bool cycle(int u) { max_cid++; int v = u; while(cid[v] != max_cid) { cid[v] = max_cid; v = pre[v]; } return v == u; } // 计算u的最小入弧,入弧起点不得在圈c中 void update(int u) { iw[u] = INF; for(int i = 0; i < n; i++) if(!removed[i] && w[i][u] < iw[u]) { iw[u] = w[i][u]; pre[u] = i; } } // 根结点为s,如果失败则返回false bool solve(int s) { memset(vis, 0, sizeof(vis)); if(dfs(s) != n) return false; memset(removed, 0, sizeof(removed)); memset(cid, 0, sizeof(cid)); for(int u = 0; u < n; u++) update(u); pre[s] = s; iw[s] = 0; // 根结点特殊处理 ans = max_cid = 0; for(;;) { bool have_cycle = false; for(int u = 0; u < n; u++) if(u != s && !removed[u] && cycle(u)){ have_cycle = true; // 以下代码缩圈,圈上除了u之外的结点均删除 int v = u; do { if(v != u) removed[v] = 1; ans += iw[v]; // 对于圈外点i,把边i->v改成i->u(并调整权值);v->i改为u->i // 注意圈上可能还有一个v'使得i->v'或者v'->i存在,因此只保留权值最小的i->u和u->i for(int i = 0; i < n; i++) if(cid[i] != cid[u] && !removed[i]) { if(w[i][v] < INF) w[i][u] = min(w[i][u], w[i][v]-iw[v]); w[u][i] = min(w[u][i], w[v][i]); if(pre[i] == v) pre[i] = u; } v = pre[v]; } while(v != u); update(u); break; } if(!have_cycle) break; } for(int i = 0; i < n; i++) if(!removed[i]) ans += iw[i]; return true; } };
邻接表写法
struct Edge{ int u,v,w; }e[MAXM]; bool MDST(int s,int n,int m){ ans=0; while(1){ //计算iw for(int i=0;i<n;i++) iw[i]=INF; for(int i=0;i<m;i++){ int u=e[i].u,v=e[i].v; if(u!=v&&e[i].w<iw[v]){ iw[v]=e[i].w; pre[v]=u; } } //如果存在孤立点,则不存在最小树形图 for(int i=0;i<n;i++) if(i!=s&&iw[i]==INF){ return false; } int cid=0; //环的编号 iw[s]=0; //root节点特殊处理,iw=0 memset(vis,-1,sizeof(vis)); memset(cir,-1,sizeof(cir)); for(int i=0;i<n;i++){ ans+=iw[i]; //判断环 int v=i; while(vis[v]!=i&&cir[v]==-1&&v!=s){ vis[v]=i; v=pre[v]; } //存在环,把环上的点编成一个号 if(vis[v]==i){ for(int u=pre[v];u!=v;u=pre[u]) cir[u]=cid; cir[v]=cid++; } } if(!cid) return true;//不存在环,得到最小树形图 //把剩下的点也重新编号 for(int i=0;i<n;i++) if(cir[i]==-1){ cir[i]=cid++; } //更新其它节点到环的距离 for(int i=0;i<m;i++){ int u=e[i].u,v=e[i].v; e[i].u=cir[u]; e[i].v=cir[v]; if(e[i].u!=e[i].v) e[i].w-=iw[v]; } //新的节点数和root编号 n=cid; s=cir[s]; } }