两个吃奶一个添下面视频_人妻第一页香蕉网_欧美xxxx少妇_妺妺窝人体色www婷婷

一、什么是最小樹形圖?

就是指有向圖上的最小生成樹,英文是Directed Minimum Spanning Tree。常用的算法是朱劉算法(也稱 Edmonds 算法),可以在O(nm)時間內(nèi)解決最小樹形圖問題。

(1)過程

對于每個點,選擇它入度最小的那條邊

如果沒有環(huán),算法終止;否則進行縮環(huán)并更新其他點到環(huán)的距離。

(2)實現(xiàn)

bool solve() {
  ans = 0;
  int u, v, root = 0;
  for (;;) {
    f(i, 0, n) in[i] = 1e100;
    f(i, 0, m) {
      u = e[i].s;
      v = e[i].t;
      if (u != v && e[i].w < in[v]) {
        in[v] = e[i].w;
        pre[v] = u;
      }
    }
    f(i, 0, m) if (i != root && in[i] > 1e50) return 0;
    int tn = 0;
    memset(id, -1, sizeof id);
    memset(vis, -1, sizeof vis);
    in[root] = 0;
    f(i, 0, n) {
      ans += 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 (int u = pre[v]; u != v; u = pre[u]) id[u] = tn;
        id[v] = tn++;
      }
    }
    if (tn == 0) break;
    f(i, 0, n) if (id[i] == -1) id[i] = tn++;
    f(i, 0, m) {
      u = e[i].s;
      v = e[i].t;
      e[i].s = id[u];
      e[i].t = id[v];
      if (e[i].s != e[i].t) e[i].w -= in[v];
    }
    n = tn;
    root = id[root];
  }
  return ans;
}

二、Tarjan 的 DMST 算法

Tarjan 提出了一種能夠在DMST 算法時間內(nèi)解決最小樹形圖問題的算法。


(1)過程

Tarjan 的算法分為收縮伸展兩個過程。接下來先介紹收縮的過程。

我們需要假設(shè)輸入的圖是滿足強連通的,如果不滿足那么就加入O(n)條邊使其滿足,并且這些邊的邊權(quán)是無窮大的。

我們需要一個堆存儲結(jié)點的入邊編號,入邊權(quán)值,結(jié)點總代價等相關(guān)信息,由于后續(xù)過程中會有堆的合并操作,這里采用 左偏樹并查集實現(xiàn)。算法的每一步都選擇一個任意結(jié)點 v,需要保證 v 不是根節(jié)點,并且在堆中沒有它的入邊。再將 v 的最小入邊加入到堆中,如果新加入的這條邊使堆中的邊形成了環(huán),那么將構(gòu)成環(huán)的那些結(jié)點收縮,我們不妨將這些已經(jīng)收縮的結(jié)點命名為超級結(jié)點,再繼續(xù)這個過程,如果所有的頂點都縮成了一個超級結(jié)點,那么收縮過程就結(jié)束了。整個收縮過程結(jié)束后會得到一棵收縮樹,之后將對它進行伸展操作。

堆中的邊總是會形成一條路徑路徑由于圖是強連通的,這個路徑必然存在,并且其中的vi可能是最初的單一結(jié)點,也可能是壓縮后的超級結(jié)點。

最初有 vo=a,其中 a 是圖中任意的一個結(jié)點,每一次選擇一條最小入邊最小入邊,如果 u 不是Tarjan 的 DMST 算法中的一個結(jié)點,那么就將結(jié)點擴展到Tarjan。如果 u 是他們其中的一個結(jié)點結(jié)點,那么就找到了一個關(guān)于結(jié)點的環(huán),再將他們收縮為一個超級結(jié)點 c。

向隊列P中放入所有的結(jié)點或超級結(jié)點,并初始選擇任意一節(jié)點 a,只要隊列不為空,就進行以下步驟:

(1)選擇 a 的最小入邊,保證不存在自環(huán),并找到另一頭的結(jié)點 b。如果結(jié)點 b 沒有被記錄過說明未形成環(huán),令a←b,繼續(xù)當前操作尋找環(huán)。

(2)如果 b 被記錄過了,就說明出現(xiàn)了環(huán)。總結(jié)點數(shù)加一,并將環(huán)上的所有結(jié)點重新編號,對堆進行合并,以及結(jié)點/超級結(jié)點的總權(quán)值的更新。更新權(quán)值操作就是將環(huán)上所有結(jié)點的入邊都收集起來,并減去環(huán)上入邊的邊權(quán)。

Tarjan

以圖片為例,左邊的強連通圖在收縮后就形成了右邊的一棵收縮樹,其中 a 是結(jié)點 1 與結(jié)點 2 收縮后的超級結(jié)點,b 是結(jié)點 3,結(jié)點 4,結(jié)點 5 收縮后的超級結(jié)點,A是兩個超級結(jié)點 a 與 b 收縮后形成的。

伸展過程是相對簡單的,以原先要求的根節(jié)點 r 為起始點,對 r 到收縮樹的根上的每一個環(huán)進行伸展。再以 r 的祖先結(jié)點 fr 為起始點,將其到根的環(huán)展開,直到遍歷完所有的結(jié)點。

代碼如下:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
#define maxn 102
#define INF 0x3f3f3f3f

struct UnionFind {
  int fa[maxn << 1];

  UnionFind() { memset(fa, 0, sizeof(fa)); }

  void clear(int n) { memset(fa + 1, 0, sizeof(int) * n); }

  int find(int x) { return fa[x] ? fa[x] = find(fa[x]) : x; }

  int operator[](int x) { return find(x); }
};

struct Edge {
  int u, v, w, w0;
};

struct Heap {
  Edge *e;
  int rk, constant;
  Heap *lch, *rch;

  Heap(Edge *_e) : e(_e), rk(1), constant(0), lch(NULL), rch(NULL) {}

  void push() {
    if (lch) lch->constant += constant;
    if (rch) rch->constant += constant;
    e->w += constant;
    constant = 0;
  }
};

Heap *merge(Heap *x, Heap *y) {
  if (!x) return y;
  if (!y) return x;
  if (x->e->w + x->constant > y->e->w + y->constant) swap(x, y);
  x->push();
  x->rch = merge(x->rch, y);
  if (!x->lch || x->lch->rk < x->rch->rk) swap(x->lch, x->rch);
  if (x->rch)
    x->rk = x->rch->rk + 1;
  else
    x->rk = 1;
  return x;
}

Edge *extract(Heap *&x) {
  Edge *r = x->e;
  x->push();
  x = merge(x->lch, x->rch);
  return r;
}

vector<Edge> in[maxn];
int n, m, fa[maxn << 1], nxt[maxn << 1];
Edge *ed[maxn << 1];
Heap *Q[maxn << 1];
UnionFind id;

void contract() {
  bool mark[maxn << 1];
  // 將圖上的每一個結(jié)點與其相連的那些結(jié)點進行記錄。
  for (int i = 1; i <= n; i++) {
    queue<Heap *> q;
    for (int j = 0; j < in[i].size(); j++) q.push(new Heap(&in[i][j]));
    while (q.size() > 1) {
      Heap *u = q.front();
      q.pop();
      Heap *v = q.front();
      q.pop();
      q.push(merge(u, v));
    }
    Q[i] = q.front();
  }
  mark[1] = true;
  for (int a = 1, b = 1, p; Q[a]; b = a, mark[b] = true) {
    // 尋找最小入邊以及其端點,保證無環(huán)。
    do {
      ed[a] = extract(Q[a]);
      a = id[ed[a]->u];
    } while (a == b && Q[a]);
    if (a == b) break;
    if (!mark[a]) continue;
    // 對發(fā)現(xiàn)的環(huán)進行收縮,以及環(huán)內(nèi)的結(jié)點重新編號,總權(quán)值更新。
    for (a = b, n++; a != n; a = p) {
      id.fa[a] = fa[a] = n;
      if (Q[a]) Q[a]->constant -= ed[a]->w;
      Q[n] = merge(Q[n], Q[a]);
      p = id[ed[a]->u];
      nxt[p == n ? b : p] = a;
    }
  }
}

ll expand(int x, int r);

ll expand_iter(int x) {
  ll r = 0;
  for (int u = nxt[x]; u != x; u = nxt[u]) {
    if (ed[u]->w0 >= INF)
      return INF;
    else
      r += expand(ed[u]->v, u) + ed[u]->w0;
  }
  return r;
}

ll expand(int x, int t) {
  ll r = 0;
  for (; x != t; x = fa[x]) {
    r += expand_iter(x);
    if (r >= INF) return INF;
  }
  return r;
}

void link(int u, int v, int w) { in[v].push_back({u, v, w, w}); }

int main() {
  int rt;
  scanf("%d %d %d", &n, &m, &rt);
  for (int i = 0; i < m; i++) {
    int u, v, w;
    scanf("%d %d %d", &u, &v, &w);
    link(u, v, w);
  }
  // 保證強連通
  for (int i = 1; i <= n; i++) link(i > 1 ? i - 1 : n, i, INF);
  contract();
  ll ans = expand(rt, n);
  if (ans >= INF)
    puts("-1");
  else
    printf("%lld\n", ans);
  return 0;
}


點贊(0)

C語言網(wǎng)提供由在職研發(fā)工程師或ACM藍橋杯競賽優(yōu)秀選手錄制的視頻教程,并配有習題和答疑,點擊了解:

一點編程也不會寫的:零基礎(chǔ)C語言學練課程

解決困擾你多年的C語言疑難雜癥特性的C語言進階課程

從零到寫出一個爬蟲的Python編程課程

只會語法寫不出代碼?手把手帶你寫100個編程真題的編程百練課程

信息學奧賽或C++選手的 必學C++課程

藍橋杯ACM、信息學奧賽的必學課程:算法競賽課入門課程

手把手講解近五年真題的藍橋杯輔導(dǎo)課程

Dotcpp在線編譯      (登錄可減少運行等待時間)