洛谷p4180严格次小生成树

题意:求严格的次小生成树

严格次小生成树:(value(e)表示边e的权值) eEM​ value(e)<eES​ value(e)(EM为最小生成树边集,ES为次小生成树边集)

就是次小生成树边权和一定要小于最小生成树,   而非严格的就不一定,也可能等于。

非严格次小生成树求法:是在最小生成树边集外   找到一条边(假设两点为u,v)(一定大于等于最小生成树里的边),替换掉最小生成树中u,v之间最大边,使得两者差值最小。  主要是要保存最小生成树两点之间最大边。

严格次小生成树与非严格相似,但是要保存两点之间  最大边和次大边, 因为最小生成树边集外 的边等于最大边时,代替次大边否则就是代替最大边。  而这里用到了lca最近公共祖先,为了保证联通

如图:假设除去紫色的边是  最小生成树,  我们的目标是用紫色的边代替   环中的小于紫边最大的边。

所以这里用到了lca,假设紫边两点为u,v,  先找到u,v的最近公共祖先点 fa,就分两边 找u,fa之间的最大小于紫边的边  和v,fa之间最大小于紫边的边

这样遍历每条不在最小生成树中的边,找到最小的   紫边与代替边的差值,加上最小生成树边权和就是答案。

最后我们来总结下步骤:

第一步:用kruskal 找到最小生成树,记算边权和,和 标记最小生成树中的边。

第二步:dfs搜索 记录最小生成树中两点的最近公共祖先, 并用同样的方式记录 两点之间最大边和最次边。

第三步:枚举不在最小生成树中的边,lca找到边两点的最近公共祖先,分两边查找最大小于这条边的边权值。

最后:找到最小的差值加上最小生成树边权和即可。

代码中一直贯彻着倍增的思想。详细可以看代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll; 
const int maxn=1e5+100;
const int maxm=3e5+100; 
struct node{
    int u,v,w,nxt;
}e[2*maxm];
struct edge{
    int x,y,z,tag;
}ee[maxm];
int head[maxn],bz[maxn][22];//bz[i][j]记录i的2^j祖先 
int maxi[maxn][22],mini[maxn][22],lg[maxn];
//maxi[i][j],记录i到2^j祖先之间的最大边权值,mini是记录次边值
//lg[i]表示log_2(i)+1,用来优化lca的,可以不用 
int depth[maxn],fa[maxn],n,m,cnt;

bool cmp(edge a,edge b)
{
    return a.z<b.z;
}

void add(int u,int v,int w)
{
    e[cnt].u=u;
    e[cnt].v=v;
    e[cnt].w=w;
    e[cnt].nxt=head[u];
    head[u]=cnt++;
}

void dfs(int f,int fath)//记录最小生成树中两点之间的最近公共祖先和最大边,次大边 
{
    bz[f][0]=fath;
    depth[f]=depth[fath]+1;    
    for(int i=1;(1<<i)<=depth[f];i++)
    {
        bz[f][i]=bz[bz[f][i-1]][i-1];//lca中的核心转换 
        mini[f][i]=max(mini[f][i-1],mini[bz[f][i-1]][i-1]);
        maxi[f][i]=max(maxi[f][i-1],maxi[bz[f][i-1]][i-1]);
        if(maxi[f][i-1]!=maxi[bz[f][i-1]][i-1])
            mini[f][i]=max(mini[f][i],min(maxi[f][i-1],maxi[bz[f][i-1]][i-1]));
    } 
    for(int i=head[f];i!=-1;i=e[i].nxt)
    {
        if(e[i].v!=fath)
        {
            maxi[e[i].v][0]=e[i].w;
            mini[e[i].v][0]=-1;
            dfs(e[i].v,f);
        }
            
    }    
}

int lca(int x,int y)//lca求最近公共祖先 
{
    if(depth[x]<depth[y])//使x深度大于y 
        swap(x,y);
    while(depth[x]>depth[y])//跳到相同深度 
        x=bz[x][lg[depth[x]-depth[y]]-1];
    if(x==y)
        return x;
    for(int k=lg[depth[x]]-1;k>=0;k--)//往上找,直到x,y跳到最近公共祖先的下一节点 
        if(bz[x][k]!=bz[y][k])
            x=bz[x][k],y=bz[y][k];        
    return bz[x][0];//返回最近公共祖先 
}

int find(int x)
{
    return fa[x]==x?x:fa[x]=find(fa[x]);
}

int qmax(int u,int v,int w)//找到u,v之间小于 w大最大边 
{
    int ans=-inf;
    for(int i=lg[depth[u]]-1;i>=0;i--)
    {
        if(depth[bz[u][i]]>=depth[v])
        {
            if(w!=maxi[u][i])//不等于最大边,取最大边 
                ans=max(ans,maxi[u][i]);
            else//等于,取次大边 
                ans=max(ans,mini[u][i]);
            u=bz[u][i];//往上跳 
        }
    }
    return ans;
}

int main()
{
    cnt=0;
    ll sum=0;
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        fa[i]=i;
    for(int i=1;i<=n;i++)//lca常数优化,lg[i]表示log_2(i)+1; 
        lg[i]=lg[i-1]+(1<<lg[i-1]==i);
    for(int i=1;i<=m;i++)
        scanf("%d%d%d",&ee[i].x,&ee[i].y,&ee[i].z);
    sort(ee+1,ee+m+1,cmp);
    for(int i=1;i<=m;i++)//kruskal
    {
        int fx=find(ee[i].x);
        int fy=find(ee[i].y);
        if(fx!=fy)
        {
            sum+=ee[i].z;
            ee[i].tag=1;
            fa[fx]=fy;
            add(ee[i].x,ee[i].y,ee[i].z);
            add(ee[i].y,ee[i].x,ee[i].z);
        }
    }
    dfs(1,0);
    int ans=inf;
    for(int i=1;i<=m;i++)//枚举不在最小生成树的边 
    {
        if(!ee[i].tag)
        {
            int lc=lca(ee[i].x,ee[i].y);
            int res=max(qmax(ee[i].x,lc,ee[i].z),qmax(ee[i].y,lc,ee[i].z));
            ans=min(ans,ee[i].z-res);//ans寻找最小差值 
        }
    }
    printf("%lld
",sum+ans);//答案就是最小生成树边权和加最小差值 
    return 0;
}
 
原文地址:https://www.cnblogs.com/xiongtao/p/11162539.html