次小生成树Tree

次小生成树Treehttps://www.luogu.org/problemnew/show/P4180

题目描述

小C最近学了很多最小生成树的算法,Prim算法、Kurskal算法、消圈算法等等。正当小C洋洋得意之时,小P又来泼小C冷水了。小P说,让小C求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的,也就是说:如果最小生成树选择的边集是EM,严格次小生成树选择的边集是ES,那么需要满足:(value(e)表示边e的权值)
\[∑e∈EMvalue(e)<∑e∈ESvalue(e)\]
这下小 C 蒙了,他找到了你,希望你帮他解决这个问题。

输入格式:

第一行包含两个整数N 和M,表示无向图的点数与边数。 接下来 M行,每行 3个数x y z 表示,点 x 和点y之间有一条边,边的权值为z。

输出格式:

包含一行,仅一个数,表示严格次小生成树的边权和。(数据保证必定存在严格次小生成树)

先Kruscal最小生成树,然后枚举非树边,权值为w,将边的两端点的路径(LCA)上的最长边用w替换,但由于我们求的是严格次小生成树,所以当最长边边权==w时,需要用严格次大边替代,因此用倍增维护LCA上的严格次大值和最大值.
具体见代码注释:

#define LL long long
#include<cstdio>
#include<algorithm>
#include<cstring>
#define RG register
using namespace std;
const int N=1e5+5,M=3e5+5;
inline LL read()
{
    RG int x=0,w=1;RG char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*w;
}
inline LL min(LL a,LL b){return a<b?a:b;}
inline LL max(LL a,LL b){return a>b?a:b;}
LL n,m,cnt,num,Cost,Ans=100000000000000;
LL last[N],par[N],dep[N],f[N][25],f1[N][25],f2[N][25];
//f[i][j]表示i节点2^j祖先的序号,f1[i][j]表示从i到f[i][j]路径上的最大边权,f1[i][j]表示严格次大边权.
bool used[M];
struct edge{//邻接表
    LL to,next,w;
}e[2*M];
struct edgm{//存边
    LL u,v,w;
}e1[M];
void insert(LL u,LL v,LL w)
{
    e[++cnt]=(edge){v,last[u],w};last[u]=cnt;
    e[++cnt]=(edge){u,last[v],w};last[v]=cnt;
}
bool cmp(edgm a,edgm b){return a.w<b.w;}
LL find(LL x){return x==par[x]?x:par[x]=find(par[x]);}
void Kruskal()
{
    for(LL i=1;i<=m;i++)
    {
        if(num==n-1)break;
        LL a=e1[i].u,b=e1[i].v;
        int fa=find(a),fb=find(b);
        if(fa==fb)continue;
        par[fa]=fb;
        Cost+=e1[i].w;//最小生成树
        num++;
        used[i]=1;//标记树中的边
        insert(a,b,e1[i].w);
    }
}
void dfs(LL now)//处理深度和f[i][0]以及f1[i][0]
{
    for(LL i=last[now];i;i=e[i].next)
    {
        LL v=e[i].to;
        if(dep[v])continue;
        dep[v]=dep[now]+1;
        f[v][0]=now;
        f1[v][0]=e[i].w;
        f2[v][0]=-100000000000000;//f2[i][0]置为-inf
        dfs(v);
    }
}
void init()
{
    dep[1]=1;
    dfs(1);
    for(LL j=1;j<=20;j++)//递推过程
        for(LL i=1;i<=n;i++)
        {
            f[i][j]=f[f[i][j-1]][j-1];
            f1[i][j]=max(f1[i][j-1],f1[f[i][j-1]][j-1]);
            if(f1[i][j-1]==f1[f[i][j-1]][j-1])f2[i][j]=max(f2[i][j-1],f2[f[i][j-1]][j-1]);
            else f2[i][j]=max(min(f1[i][j-1],f1[f[i][j-1]][j-1]),max(f2[i][j-1],f2[f[i][j-1]][j-1]));
        }
}
LL LCA(LL a,LL b,LL w)
{
    LL dwq[200];//将可能的取值放进数组中,排序方便处理
    LL ct=0;
    memset(dwq,0,sizeof(dwq));
    if(dep[b]>dep[a])swap(a,b);
    for(LL i=20;i>=0;i--)//倍增
    {
        if(f[a][i]&&dep[f[a][i]]>=dep[b])
        {
            dwq[++ct]=f1[a][i];
            dwq[++ct]=f2[a][i];
            a=f[a][i];
        }
    }
    if(a!=b)
    {
        for(LL i=20;i>=0;i--)
            if(f[a][i]&&f[b][i]&&f[a][i]!=f[b][i])
            {
                dwq[++ct]=f1[a][i];
                dwq[++ct]=f2[a][i];
                dwq[++ct]=f1[b][i];
                dwq[++ct]=f2[b][i];
                a=f[a][i];
                b=f[b][i];
            }
        dwq[++ct]=f1[a][0];
        dwq[++ct]=f1[b][0];
        dwq[++ct]=f2[a][0];
        dwq[++ct]=f2[b][0];
    }
    sort(dwq+1,dwq+ct+1);
    for(LL i=ct;i>=1;i--)if(dwq[i]<w)return dwq[i];
}
int main()
{
    n=read();m=read();
    for(LL i=1;i<=n;i++)par[i]=i;
    for(LL i=1;i<=m;i++)e1[i]=(edgm){read(),read(),read()};
    sort(e1+1,e1+m+1,cmp);
    Kruskal();
    init();
    for(LL i=1;i<=m;i++)
    {
        if(used[i])continue;
        LL a=e1[i].u,b=e1[i].v,w=e1[i].w;
        Ans=min(Ans,Cost-LCA(a,b,w)+w);//取最小值
    }
    printf("%lld\n",Ans);
    return 0;
}

转载于:https://www.cnblogs.com/sdzwyq/p/8425656.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值