1. 程式人生 > >[BJOI2010] 嚴格次小生成樹

[BJOI2010] 嚴格次小生成樹

題目連結
一個嚴格次小生成樹的模板題。

看到次小生成樹,我們有一個很直觀的想法就是先構造出來最小生成樹,然後將這個最小生成樹上面最大的一條邊替換成和它值最相近而且比他大的邊。

那麼首先就是用kruskal演算法算出來最小生成樹,我們稱在這個最小生成樹上面的邊為樹邊(打上標記),不在的邊為非樹邊

之後就是用非樹邊替換樹邊了。

考慮怎麼替換。我們可以通過列舉每一條非樹邊,然後找到這條邊對應的兩端節點在最小生成樹上的最大邊權,然後替換。

正確性顯然,因為非樹邊肯定比樹邊劣,而當我們替換了樹邊之後,肯定是次小的。

但是要注意一點就是這個題是嚴格次小的,所以我們在記錄最大值的時候還要記錄次大值。

然後就是如何找最小生成樹上兩個點之間的邊權最大值和次大值。觀察資料範圍,3e5的資料顯然不能一個一個暴力,那麼就是倍增或者樹剖優化了。

在這裡給出倍增的做法,程式碼如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define MAXN 300010
using namespace std;
int n,m,t;
long long res=(long long)1e15,sum;
int head[MAXN],dis[MAXN],fa[MAXN],g[MAXN][32],done[MAXN],dep[MAXN],maxx1[MAXN][32],maxx2[MAXN][32];
struct Edge{int nxt,to,dis,from;}edge[MAXN<<1],pre[MAXN<<1];
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline bool cmp(struct Edge x,struct Edge y){return x.dis<y.dis;}
inline void add(int from,int to,int dis){edge[++t].nxt=head[from],edge[t].to=to,edge[t].dis=dis,head[from]=t;}
inline void init()
{
    for(int k=1;k<=21;k++)
        for(int i=1;i<=n;i++)
        {
            g[i][k]=g[g[i][k-1]][k-1];
            maxx1[i][k]=max(maxx1[g[i][k-1]][k-1],maxx1[i][k-1]);
            if(maxx1[i][k-1]==maxx1[g[i][k-1]][k-1])
                maxx2[i][k]=max(maxx2[i][k-1],maxx2[g[i][k-1]][k-1]);
            else
            {
                maxx2[i][k]=min(g[i][k-1],maxx1[g[i][k-1]][k-1]);
                maxx2[i][k]=max(maxx2[i][k],max(maxx2[i][k-1],maxx2[g[i][k-1]][k-1]));
            }
        }
}
inline void kruskal()
{
    int cnt=0;
    for(int i=1;i<=m;i++)
    {
        int a=find(pre[i].from),b=find(pre[i].to);
        if(a!=b) 
        {
            fa[a]=b,done[i]=1;
            cnt++,sum+=pre[i].dis;
            add(pre[i].from,pre[i].to,pre[i].dis);
            add(pre[i].to,pre[i].from,pre[i].dis);
        }
        if(cnt==n-1) return;
    }
}
inline void dfs(int now)
{
    for(int i=head[now];i;i=edge[i].nxt)
    {
        int v=edge[i].to;
        if(v!=g[now][0])
            g[v][0]=now,maxx1[v][0]=edge[i].dis,dep[v]=dep[now]+1,dfs(v);
    }
}
inline void calc(int x,int &m1,int &m2,int k)
{
    if(maxx1[x][k]>m1) m2=m1,m1=maxx1[x][k];
    else if(maxx1[x][k]<m1) m2=max(m2,maxx1[x][k]);
    m2=max(m2,maxx2[x][k]);
}
inline void lca(int x,int y,int w)
{
    int cur_max1=0,cur_max2=0;
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=21;i>=0;i--)
        if((dep[x]-dep[y])&(1<<i))
            calc(x,cur_max1,cur_max2,i),x=g[x][i];
    if(x==y) {res=min(res,1ll*(w==cur_max1?w-cur_max2:w-cur_max1)); return;}
    for(int i=21;i>=0;i--)
        if(g[x][i]!=g[y][i])
            calc(x,cur_max1,cur_max2,i),calc(y,cur_max1,cur_max2,i),x=g[x][i],y=g[y][i];
    calc(x,cur_max1,cur_max2,0),calc(y,cur_max1,cur_max2,0);
    res=min(res,1ll*(w==cur_max1?w-cur_max2:w-cur_max1));
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("ce.in","r",stdin);
    #endif
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d%d%d",&pre[i].from,&pre[i].to,&pre[i].dis);
    for(int i=1;i<=n;i++) fa[i]=i;
    sort(&pre[1],&pre[m+1],cmp);
    kruskal();
    dep[1]=1;
    dfs(1);
    init();
    for(int i=1;i<=m;i++)
    {
        if(done[i]==1) continue;
        lca(pre[i].from,pre[i].to,pre[i].dis);
    }
    printf("%lld\n",sum+res);
    return 0;
}