次小生成樹

一行小字。這篇博客是個人gg以前寫的博客,一直想寫博客,可是直到gg,都沒寫幾篇博客。我再把這篇博客翻出來的時候,不知已通過了半年仍是一年了。博客園的博客系統作的不錯,就是數學公式和一些圖片會亂,圖片的話手動上傳一下還ok。數學公式不影響閱讀,你們自行理解就ok嚶嚶~

前置知識

  • 最小生成樹Kurskal算法c++

  • 最近公共祖先LCA!算法

引入問題

定義

一個有 n 個結點的連通圖的生成樹是原圖的極小連通子圖,且包含原圖中的全部 n 個結點,而且有保持圖連通的次少的邊。數組

模板題

洛谷P4180 [BJWC2010]嚴格次小生成樹markdown

解決方法

想法

把全部生成樹按照權值之和從小到小排序,求排在第二位的生成樹。注意,若是最小生成樹不惟一,次小生成樹的權值和最小生成樹相同。函數

解法一

次小生成樹不會和最小生成樹相同,所以能夠枚舉最小生成樹中不在次小生成樹中出現的邊。 注意最小生成樹只有n-1條邊,因此只需枚舉n-1次。每次在剩下的邊裏,求一次最小生成樹。學習

步驟

  1. Kruskal算法求出最小生成樹優化

  2. 枚舉最小生成樹的每一條邊,對這條邊作標記,再進行一次Kruskal算法,Kruskal算法中,跳過被標記的邊,求最小生成樹,記錄答案。spa

  3. 去步驟2中所記錄答案的最小值即爲次小生成樹的邊權之和。(可直接在步驟2中進行)調試

複雜度

  1. $O(M\log M+M)$code

  2. $O(NM)$

  3. $O(N)$

總複雜度$O(NM)$

有沒有更快些的解決方法呢?

補充知識

可行交換與臨集

- T爲圖G的一棵生成樹,對於非樹邊a和樹邊b,插入邊a而且刪除邊b的操做記爲(+a,-b)

- 若是T+a-b仍然是一棵生成樹,稱(+a,-b)是一個可行交換

- 由T進行一次可行交換之後獲得的新的生成樹的集合稱爲T的臨集

顯然,可得定理:次小生成樹在最小生成樹的臨集中

更好的解法

- 枚舉要加入哪條新邊,在最小生成樹中加入一條邊u-v之後,圖上會出現一條迴路,若是想保持該圖爲生成樹,需刪除一條邊,刪除的邊必須是在最小生成樹上u到v的路徑上,若是想要刪除邊後的新的生成樹有可能爲次小生成樹,必需要求刪除的邊這條路徑的最長邊。

如圖,枚舉出一條不在最小生成樹上的邊E1,若是鏈接E1,E1兩端點爲(1,4),若是想鏈接E1後依然保證新圖爲生成樹,需刪除路徑(1,4)上一條最小生成樹上的邊。爲了求出次小生成樹,咱們選擇刪除原樹上的儘量大的邊,即刪除邊(3,4),鏈接邊E1。

如圖爲新生成的樹。

總結一下,新的算法是這樣的

  • 求出最小生成樹以後,枚舉每個不在樹上的邊,對於邊(u,v),連上此邊,刪去原來樹上路徑(u,v)上最長的邊。
  • 題目中要求的是嚴格次小生成樹,所以,當原來樹上路徑(u,v)上最長的邊的權值等於新連邊(u,v)時,是不符合要求的,咱們須要找次長邊。

那麼枚舉每一條邊複雜度是$O(M)$,對每一條邊用dfs求(u,v)上的最長&次長邊,複雜度是$O(N)$,總複雜度是$O(MN)$沒有遍低呀~

所以,我沒須要對算法進行優化,能夠經過dfs$O(N)$來預處理每一個點到其k代父節點的路徑上的最長邊和次長邊,再經過lca倍增的方法在$O(\log N)$複雜度下求出u,v兩點之間的最長邊和次長邊。

預處理

對於每個點u,咱們記記錄anc[u][k]數組,u是節點編號,k表明其$2^k $代父節點。

記錄m1[u][k]數組,其中,u是節點編號,k表明從u節點往上$2k$個點,數組的值是u點到u節點上方$2k $個點間的最長邊,可得狀態轉移方程

$$m1[u][k]=\max(m1[u][k-1],m1[anc[u][k-1]][k-1])$$

記錄m2[u][k]數組,u,k含義同m1,數組的值是u點到u節點上方$2^k $個點間的次長邊,可得狀態轉移方程

$$m2[u][k]=\begin{cases} \max(m2[u][k-1],m2[anc[u][k-1][k-1]), & \text{if }m1[u][k-1]\ne m1[anc[u][k-1]][k-1] \ \min(m1[u][k-1],m1[anc[u][k-1][k-1]), & \text{if }m1[u][k-1]= m1[anc[u][k-1]][k-1] \end{cases}$$

通過一次dfs後,咱們便可預處理完成以上內容。

遍歷$N$個點,每一個點要進行$\log N$次計算,複雜度爲 $O(N\log N)$

LCA同時求值

對於點u和v,咱們在有預處理的狀況下想求其兩點之間路徑的最長邊和次長邊,可進行如下操做

用倍增法求LCA的方式將兩個點向上跳,跳的同時記錄m1和m2的值。

向上跳的作法與倍增法求LCA相同,若是沒有了解,請先學習倍增法求LCA,此處不作過多介紹。

具體代碼以下:

/*參數ma爲要新建的邊的權值,由於要求嚴格次小生成樹,
 因此在原樹中刪除的邊必須嚴格小於新建的邊的權值,
 即lca函數的返回值(也就是在原樹中刪除的邊的權值)需嚴格小於參數ma。*/
long long findBig(long long u,long long i,long long ma){
    if(m1[u][i]!=ma){
        return m1[u][i];
    }
    return m2[u][i];
}
long long lca(long long u,long long v,long long ma){
    if(dep[u]<dep[v]){
        swap(u,v);
    }
    long long res = -INF;
    for(int i = 20;i>=0;--i){
        if(dep[ancestor[u][i]]<dep[v]){
            continue;
        }
        res = max(res,findBig(u,i,ma));
        u = ancestor[u][i];
    }
    if(u==v){
        return res;
    }
    for(int i = 20;i>=0;--i){
        if(ancestor[u][i]!=ancestor[v][i]){
            res = max(res,findBig(u,i,ma));
            res = max(res,findBig(v,i,ma));
            u = ancestor[u][i];
            v = ancestor[v][i];
        }
    }
    res = max(res,findBig(u,0,ma));
    res = max(res,findBig(v,0,ma));
    return res;
}

步驟

  1. Kruskal算法求出最小生成樹
  2. dfs預處理
  3. 枚舉每一條不在最小生成樹上的邊,求出最小生成樹上的邊權之和加上該邊之和,減去該邊所連兩點在原樹上的路徑間的最長邊(次長邊),即爲新的生成樹的邊權之和,記錄答案。
  4. 在步驟3中,所記錄答案的最小值即爲次小生成樹的邊權之和。(可直接在步驟3中進行)

複雜度

  1. $O(M\log M+M)$
  2. $O(N\log N )$
  3. $O(M\log N)$
  4. $O(N)$

總複雜度爲:$O(MlogN)$

具體代碼

//
// Created by SeverusNg on 2020/7/24.
//

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>

const long long MAXN = 100005;
const long long MAXM = 300005;
const  long long INF = 0x3f3f3f3ff3f3f3f;
using namespace std;

struct edge{
    long long u,v,w;
    int next;
    bool operator<(const edge &e)const{
        return w<e.w;
    }
} ed[MAXM*2],pool[MAXM];
int head[MAXN];
int topE;
void addE(int u,int v,int w){
    ed[++topE].u = u;
    ed[topE].v = v;
    ed[topE].w = w;
    ed[topE].next = head[u];
    head[u] = topE;
    return;
}
int fa[MAXN];

int find(int u){
    if(fa[u]==u){
        return fa[u];
    }
    fa[u] = find(fa[u]);
    return fa[u];
}
void merge(int u,int v){
    u = find(u);
    v = find(v);
    fa[u] = v;
    return;
}

bool use[MAXM];

long long N,M;
long long mst;
void kruskal(){
    sort(pool+1,pool+M+1);
    for(int i = 1;i<=M;++i){
        long long u = pool[i].u;
        long long v = pool[i].v;
        long long w = pool[i].w;
        if(find(u)!=find(v)){
            mst += w;
            merge(u,v);
            addE(u,v,w);
            addE(v,u,w);
            use[i]=true;
        }
    }
    return;
}
int dep[MAXN];

int ancestor[MAXN][25];
long long m1[MAXN][25];
long long m2[MAXN][25];
long long findBig(long long u,long long i,long long ma){
    if(m1[u][i]!=ma){
        return m1[u][i];
    }
    return m2[u][i];
}
void dfs(long long u,long long father,long long w){
    ancestor[u][0] = father;
    dep[u] = dep[father]+1;
    m1[u][0]=w;
    m2[u][0]=-INF;
    for(int i = 1;i<=20;++i){
        ancestor[u][i]=ancestor[ancestor[u][i-1]][i-1];
        m1[u][i]=max(m1[u][i-1],m1[ancestor[u][i-1]][i-1]);
        m2[u][i]=max(m2[u][i-1],m2[ancestor[u][i-1]][i-1]);
        if(m1[u][i-1]!=m1[ancestor[u][i-1]][i-1]){
            m2[u][i]=max(m2[u][i],min(m1[u][i-1],m1[ancestor[u][i-1]][i-1]));
        }
    }
    for(int i = head[u];i!=0;i=ed[i].next){
        long long u,v,w;
        u = ed[i].u;
        v = ed[i].v;
        w = ed[i].w;
        if(v==father){
            continue;
        }
        dfs(v,u,w);
    }
    return;
}

long long lca(long long u,long long v,long long ma){
    if(dep[u]<dep[v]){
        swap(u,v);
    }
    long long res = -INF;
    for(int i = 20;i>=0;--i){
        if(dep[ancestor[u][i]]<dep[v]){
            continue;
        }
        res = max(res,findBig(u,i,ma));
        u = ancestor[u][i];
    }
    if(u==v){
        return res;
    }
    for(int i = 20;i>=0;--i){
        if(ancestor[u][i]!=ancestor[v][i]){
            res = max(res,findBig(u,i,ma));
            res = max(res,findBig(v,i,ma));
            u = ancestor[u][i];
            v = ancestor[v][i];
        }
    }
    res = max(res,findBig(u,0,ma));
    res = max(res,findBig(v,0,ma));
    return res;
}
int main(){
    scanf("%lld%lld",&N,&M);
    for(int i = 1;i<=N;++i){
        fa[i] = i;
    }
    for(int i = 1;i<=M;++i){
        scanf("%lld%lld%lld",&pool[i].u,&pool[i].v,&pool[i].w);
    }
    kruskal();
    dfs(1,0,-INF);
    long long ans = INF;
    for(int i = 1;i<=M;++i){
        if(use[i]){
            continue;
        }
        long long u = pool[i].u;
        long long v = pool[i].v;
        long long w = pool[i].w;
        ans = min(ans,mst+w-lca(u,v,w));
    }

    printf("%lld\n",ans);
    return 0;
}

嚶嚶嚶,完結撒花~

啊,從建博客,各類調試,到寫完這一篇文章,再加上摸魚,擡頭一看,天已經亮了,搞了一個通宵~

不過看到成果,心裏仍是十分高興的~

若有錯誤,但願巨佬您可以在評論區指正,感激涕零~

相關文章
相關標籤/搜索