最小树形图 朱刘算法

  最小树形图,就是给有向带权图中指定一个特殊的点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];
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值