码迷,mamicode.com
首页 > 其他好文 > 详细

秘密的牛奶运输

时间:2019-11-04 21:48:20      阅读:76      评论:0      收藏:0      [点我收藏+]

标签:描述   kruskal   problem   最大   代码   tin   clu   code   amp   

https://loj.ac/problem/10068

题目描述

??给出一张图,求它的严格次小生成树。

思路

??我们考虑最小生成树和严格次小生成树的关系,由于它只要求出边权和,所以我们可以只求出一种最小生成树。我们假如已知最小生成树\(T\),那么其中一种严格次小生成树必定是断掉\(T\)一条边,再加入连接\(T1\)\(T2\)的第二边权的边,这样必定有一种是次小生成树。或者说,我们不断尝试加入一条边,这样就和最小生成树形成一棵基环树,我们从环中去掉最长的一条边(除加入的边),这样也会有一种是次小生成树。

??我们先来证明算法思路的正确性。首先,对于第一种思路我们很容易证明这一定还是一棵生成树,而对于每两个连通支的合并,除一次合并外选择都是最小边权,所以必定有一次合并会是次小生成树。而对于第二种思路,它也肯定是一棵生成树,我们也是类似的思想,相当于在\(prim\)时除一条边外选择了次小边,所以也是一棵次小生成树。其实两个思路本质是相同的。

??求次小生成树算法:\(①Kruskal\)\(②\)倍增;\(③Prim\)

??基本的公式是\(SecondMST=min(MST+w(u, v)-Max(u, v))\) \(((u, v) MST)\)

??我们先讲倍增的思路。加入我们加入了\((u,v)\),我们必定要找到形成环上的的最大边和严格次大边,这样如果这条边的边权等于最大边就删去次大边,否则就删去最大边。对于求这个我们可以用倍增维护,每次维护最大边时比较简单,而次大边需要分两种:如果最大边相同,就取次大边中较大值;否则就取较小的最大边和另一个次大边中的较大值。

??再说\(prim\)\(Kruskal\)的思路。它们的基本思想都是在进行最小生成树时维护一个\(Max\)数组,表示最小生成树上两点路径上的最大边权。其实这个和倍增是有所重叠的,因为树上两点的边权可以用倍增求。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=600,MAXM=1e4+10;
const ll INF=(1LL<<50);
struct Edge
{
    int x,y,w;
}e[MAXM];
int fa[MAXN],nxt[MAXM],head[MAXN],to[MAXM],w[MAXM];
int f[MAXN][21],dep[MAXN],tot;
ll g[MAXN][21][2];
bool used[MAXM];
bool cmp(Edge x,Edge y)
{
    return x.w<y.w;
}
int find(int x)
{
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
void add_edge(int x,int y,int v)
{
    nxt[++tot]=head[x];
    head[x]=tot;
    to[tot]=y;
    w[tot]=v;
}
int cal(int x,int k)
{
    int y=f[x][k];
    if(g[x][k][0]==g[y][k][0])
        return max(g[x][k][1],g[y][k][1]);
    else if(g[x][k][0]<g[y][k][0])
        return max(g[x][k][0],g[y][k][1]);
    else return max(g[x][k][1],g[y][k][0]);
}
void dfs(int u,int father)
{
    dep[u]=dep[father]+1;
    for(int i=0;i<=19;i++)
    {
        f[u][i+1]=f[f[u][i]][i];
        g[u][i+1][0]=max(g[u][i][0],g[f[u][i]][i][0]);
        g[u][i+1][1]=cal(u,i);
    }
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==father)continue ;
        f[v][0]=u;
        g[v][0][0]=w[i];
        g[v][0][1]=-INF;
        dfs(v,u);
    }
}
int LCA(int x,int y)
{
    if(dep[x]<dep[y])swap(x,y);
    for(int i=20;i>=0;i--)
    {
        if(dep[f[x][i]]>=dep[y])x=f[x][i];
        if(x==y)return x;
    }
    for(int i=20;i>=0;i--)
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i];
            y=f[y][i];
        }
    return f[x][0];
}
ll qmax(int x,int y,int w)
{
    ll res=-INF;
    for(int i=20;i>=0;i--)
        if(dep[f[x][i]]>=dep[y])
        {
            if(w!=g[x][i][0])res=max(res,g[x][i][0]);
            else res=max(res,g[x][i][1]);
            x=f[x][i];
        }
    return res;
}
int main() 
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].w);
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=n;i++)
        fa[i]=i;
    int cnt=0;tot=0;
    ll sum=0;
    for(int i=1;i<=m;i++)
    {
        int fx=find(e[i].x),fy=find(e[i].y);
        if(fx!=fy)
        {
            fa[fx]=fy;
            cnt++;
            add_edge(e[i].x,e[i].y,e[i].w);
            add_edge(e[i].y,e[i].x,e[i].w);
            used[i]=1;
            sum+=e[i].w;
            if(cnt==n-1)break ;
        }
    }
    dfs(1,0);
    ll ans=INF;
    for(int i=1;i<=m;i++)
        if(!used[i])
        {
            int lca=LCA(e[i].x,e[i].y);
            int val=max(qmax(e[i].x,lca,e[i].w),qmax(e[i].y,lca,e[i].w));
            ans=min(ans,sum-val+e[i].w);
        }
    printf("%lld",ans);
    return 0;
}

秘密的牛奶运输

标签:描述   kruskal   problem   最大   代码   tin   clu   code   amp   

原文地址:https://www.cnblogs.com/fangbozhen/p/11794869.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!