MnZn,次小生成树RE求助
查看原帖
MnZn,次小生成树RE求助
453762
天下人间楼主2021/10/31 11:52


#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
#define int long long
#define CCF 0
#define FUCK return
struct ba//邻接表边结构体
{
    int from;
    int to;
    int nex;
    int lon;
    bool use;
}bian[100001];
int f[100001][21]={};//倍增,f[i][j]表示点i的2^j辈祖先
int fa[100001]={};//父节点
int g[100001][21][2]={};//最后一维0表示i到f[i][j]的最小生成树的最大边,1表示次大边
struct hd
{
    int fir;
    int las;
    int num;
}head[100001];//邻接表结构体
bool bmp(ba a,ba b)
{
    return a.lon<b.lon;
}//kruskal找最小边
int p=0;//邻接表连边的条数
void lian(int a,int b,int c)//邻接表连边
{
    p++;
    if(head[a].num)
    {
        bian[head[a].las].nex=p;
        head[a].num++;
        head[a].las=p;
    }
    else
    {
        head[a].fir=p;
        head[a].las=p;
        head[a].num=1;
    }
}
int fin(int x)//并查集
{
    if(fa[x]==x)
        return x;
    return fa[x]=fin(fa[x]);
}
int dep[100001]={};//节点深度
void dfs(int a,int fat)//dfs求深度和父节点
{
    fa[a]=fat;
    dep[a]=dep[fat]+1;
    for(int i=head[a].fir;i;i=bian[i].nex)
    {
        if(bian[i].to!=fat&&bian[i].use==1)
        {
            dfs(bian[i].to,a);
            g[a][0][0]=bian[i].lon;//只有一条边,最大即边长,次大-99999999(后面初始化)
        }
    }
    return;
}
void zuida(int a,int b,int &ans1,int &ans2)//找a到b的最长和次长边,ans1,ans2保存(LCA)
{
    if(dep[a]<dep[b])
        swap(a,b);
    for(int i=20;i>=0;i--)
    {
        if(a==b)
        return;
        if(dep[a]==dep[b])
        break;
        if(dep[f[a][i]]>=dep[b])
        {
            ans1=max(g[a][i][0],ans1);

            ans2=max(g[a][i][1],ans2);

            a=f[a][i];

        }
    }//统一深度
    for(int i=20;i>=0;i--)
    {
        if(a==b)
        return;
        if(f[a][i]!=f[b][i])
        {
            ans1=max(g[a][i][0],ans1);
            ans1=max(g[b][i][0],ans1);
            ans2=max(g[a][i][1],ans2);
            ans2=max(g[b][i][1],ans2);
            a=f[a][i];
            b=f[b][i];
        }
    }
    ans1=max(g[a][0][0],ans1);
    ans1=max(g[b][0][0],ans1);
    ans2=max(g[a][0][1],ans2);
    ans2=max(g[b][0][1],ans2);//求最大与次大

}
#undef int
int main()
{
    #define int long long
    int n,m;
    scanf("%lld %lld",&n,&m);
    for(int i=1;i<=m;i++)//初始化,因为i条边,编号2*i-1,2*i
    {
        int a,b,c;
        scanf("%lld %lld %lld",&a,&b,&c);
        bian[(i<<1)-1].from=a;
        bian[(i<<1)-1].to=b;
        bian[(i<<1)-1].lon=c;
        bian[(i<<1)].from=b;
        bian[i<<1].to=a;
        bian[i<<1].lon=c;
    }
    /*for(int i=1;i<=2*m;i++)
    {

    }*/
    sort(bian+1,bian+1+m+m,bmp);//排序

    for(int i=1;i<=m;i++)//邻接表存边
    {
        lian(bian[i<<1-1].from,bian[i<<1-1].to,bian[i<<1-1].lon);
        lian(bian[i<<1].from,bian[i<<1].to,bian[i<<1].lon);
    }
    for(int i=1;i<=n;i++)//并查集初始化
    {
        fa[i]=i;
    }
    int temp=0;//kruskal求最小生成树边数
    int ans=0;//最小生成树大小
    for(int i=1;i<=m&&temp<=n-1;i++)
    {
        if(fin(bian[i<<1].from)!=fin(bian[i<<1].to))//如果祖先不同
        {
            temp++;
            ans+=bian[i<<1].lon;
            fa[fin(bian[i<<1].to)]=bian[i<<1].from;
            bian[i<<1].use=bian[(i<<1)-1].use=1;//表示这条边和反向边都被用过
        }
    }
    dfs(1,0);//求父节点
    int ans2=21000000000000000;
    for(int i=1;i<=n;i++)//初始化
    {
        f[i][0]=fa[i];//2^0辈肯定是父节点啦
        g[i][0][1]=-100000000000;
    }
    for(int i=1;i<=20;i++)//求f,g
    {
        for(int j=1;j<=n;j++)
        {
            f[j][i]=f[f[j][i-1]][i-1];
            g[j][i][0]=max(g[j][i-1][0],g[f[j][i-1]][i-1][0]);
            if(g[j][i-1][0]==g[f[j][i-1]][i-1][0])
                g[i][j][1]=max(g[i][j-1][1],g[f[i][j-1]][j-1][1]);
            if(g[j][i-1][0]<g[f[j][i-1]][i-1][0])
                g[i][j][1]=max(g[i][j-1][0],g[f[i][j-1]][j-1][1]);
            if(g[j][i-1][0]>g[f[j][i-1]][i-1][0])
                g[i][j][1]=max(g[i][j-1][1],g[f[i][j-1]][j-1][0]);
        }
    }
    for(int i=1;i<=m;i++)
    {
        if(!bian[i<<2].use)//求次小生成树
        {
            int ansss;
            int anssss;
            zuida(bian[i<<2].from,bian[i<<2].to,ansss,anssss);
            if(ansss==bian[i<<2].lon)//如果最大边和最小生成树的最大边一样,用anssss替换
            {
                ans2=min(ans2,ans+bian[i<<2].lon-anssss);
            }
            else//否则用ansss替换
            {
                ans2=min(ans2,ans+bian[i<<2].lon-ansss);
            }
        }
    }
    printf("%lld",ans2);//输出
    FUCK CCF;//和别人学的
}


2021/10/31 11:52
加载中...