• 严格次小生成树学习笔记


    前言

    严格次小生成树,顾名思义,就是在联通图上选择一些边构成一棵树,使这棵树边权和严格次小。

    第一步:求出最小生成树

    要求严格次小生成树,我们就要先求出最小生成树(Prim)(Kruskal)等算法都可以,我用的是(Kruskal)),在求最小生成树的过程中,还要给每条使用过的边打一个标记,代码如下:

    inline void Kruskal()//求最小生成树
    {
    	register int res=0,tot=1;//res记录最小生成树的边权和,tot记录已经在树上的点数
    	for(sort(e+1,e+m+1,cmp),i=1;i<=m&&tot<n;++i)//先将边按权值排序,只要已经在树上的点数小于n,就不断枚举下一条边
        {
            static LL fx,fy;
            if((fx=getfa(e[i].from))^(fy=getfa(e[i].to))) f[fy]=fx,res+=e[i].val,++tot,e[i].used=1,v[e[i].from].push_back(i),v[e[i].to].push_back(i);//合并两个联通快,更新res和tot,并标记这条边已使用
        }	
    }
    

    最小生成树的主要思想

    讲如何求严格次小生成树之前,还要先回顾一下最小生成树的主要思想。

    比如说(Kruskal),它的思想就是,贪心每次选择权值最小的一条边,判断这条边两边的点是否在同一个联通块内,也就是判断加上这条边之后是否会构成环

    从最小生成树的主要思想而引发得到的思路

    首先,我们应该有一个比较显然的想法:如果我们要求出严格次小生成树,那么肯定就是用一条边去替换最小生成树上的一条边。

    现在假设我们已经选好了一条边(这可以拿来枚举),那么应该替换掉那一条边呢?

    添上一条边肯定会使原来的树上形成一个环,要让这棵树重新变回树,那么显然要删掉这个环上的一条边,而无论删掉环上的哪一条边,结果都是一棵树。

    既然删哪条边都无所谓,那么由于贪心的思想,显然是尽量删掉边权较大的边。

    又因为我们是求严格次小生成树,因此,只要删掉边权严格小于新加上的那条边的边权边权最大的一条边。

    如何确定这条边——倍增(LCA)

    现在我们已经知道应该删掉的边是什么样的了,但是如何确定这条边到底是哪条边呢?

    我们可以假设新加上的这条边的左右端点分别为(u)(v),那么,要删掉的边必然位于最小生成树上(u)(v)的路径之间。也就是肯定位于(u)(lca(u,v))(v)(lca(u,v))的其中一条路径上。

    那么我们就可以考虑倍增(LCA)

    同经典的倍增(LCA)一样,我们用(fa_{i,j})来记录(i)的第(2^j)个祖先,然后还要额外加上两个数组(Max1_{i,j})(Max2_{i,j})分别表示节点(i)到其第(2^j)个祖先的路径上边权最大的边的边权边权严格次大的边的边权

    为什么只需记录最大和严格次大的边权,而不需记录第三大、第四大、第五大?

    证明: 由于最小生成树基于贪心,因此,这些边中最大的边也不会大于新加入的这条边,所以严格次大的边一定小于新加入的这条边。

    既然这样,对于新加入的一条边((u,v)),我们只需在求出它们的(LCA)的同时计算出(u)(v)(lca(u,v))的路径上小于((u,v))权值的最大边权即可。

    代码如下:

    inline LL MAX(LL x,LL a,LL b,LL val)//求出a到其第2^b个父亲的路径上边权小于val的最大值与x的最大值
    {
    	if(val>Max1[a][b]) return max(x,Max1[a][b]);
    	return max(x,Max2[a][b]);
    }
    inline LL Get(LL x,LL y,LL val)//求出x和y路径之间小于val的最大边权
    {
    	register LL i,res=0;//res记录答案
    	if(Depth[x]<Depth[y]) swap(x,y);//以下为倍增LCA的模板,只不过在每次更新节点的同时加上一个更新小于val的最大边权的操作即可
    	for(i=0;Depth[x]^Depth[y];++i) if((Depth[x]^Depth[y])&(1<<i)) res=MAX(res,x,i,val),x=fa[x][i];
    	if(!(x^y)) return res;
    	for(i=0;fa[x][i]^fa[y][i];++i);
    	for(;i>=0;--i) if(fa[x][i]^fa[y][i]) res=MAX(MAX(res,x,i,val),y,i,val),x=fa[x][i],y=fa[y][i];
    	return MAX(MAX(res,x,0,val),y,0,val); 
    }
    

    代码

    最后,贴一下洛谷板子题的代码:

    #include<bits/stdc++.h>
    #define max(x,y) ((x)>(y)?(x):(y))
    #define min(x,y) ((x)<(y)?(x):(y))
    #define abs(x) ((x)<0?-(x):(x))
    #define LL long long
    #define ull unsigned long long
    #define swap(x,y) (x^=y,y^=x,x^=y)
    #define tc() (A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++)
    #define pc(ch) (pp_<100000?pp[pp_++]=(ch):(fwrite(pp,1,100000,stdout),pp[(pp_=0)++]=(ch)))
    #define N 100000
    #define M 300000
    #define MOD 31011
    LL pp_=0;char ff[100000],*A=ff,*B=ff,pp[100000];
    using namespace std;
    LL n,m,Depth[N+5],f[N+5],fa[N+5][20],Max1[N+5][20],Max2[N+5][20];
    struct edge
    {
    	LL from,to,val,used;
    }e[M+5];
    vector<LL> v[N+5];
    inline void read(LL &x)
    {
    	x=0;LL f=1;static char ch;
    	while(!isdigit(ch=tc())) f=ch^'-'?1:-1;
    	while(x=(x<<3)+(x<<1)+ch-48,isdigit(ch=tc()));
    	x*=f;
    }
    inline void write(LL x)
    {
    	if(x<0) pc('-'),x=-x;
    	if(x>9) write(x/10);
    	pc(x%10+'0');
    }
    inline LL getfa(LL x)
    {
    	return f[x]^x?f[x]=getfa(f[x]):x;
    }
    inline bool cmp(edge x,edge y)
    {
    	return x.val<y.val||(x.val==y.val&&x.to<y.to);
    }
    inline void GetRt(LL x)
    {
    	register LL i,sz=v[x].size();
    	for(i=0;i<sz;++i) 
    	{
    		register LL nxt=e[v[x][i]].from^x?e[v[x][i]].from:e[v[x][i]].to,val=e[v[x][i]].val;
    		if(!(fa[x][0]^nxt)) continue;
    		fa[nxt][0]=x,Max1[nxt][0]=val,Depth[nxt]=Depth[x]+1,GetRt(nxt);
    	}
    }
    inline void Init()
    {
    	register LL i,j;
    	for(GetRt(Depth[1]=1),j=1;j<20;++j)
    	{
    		for(i=1;i<=n;++i)
    		{
    			fa[i][j]=fa[fa[i][j-1]][j-1],Max1[i][j]=max(Max1[i][j-1],Max1[fa[i][j-1]][j-1]),Max2[i][j]=max(Max2[i][j-1],Max2[fa[i][j-1]][j-1]);
    			if(Max1[i][j-1]^Max1[fa[i][j-1]][j-1]) Max2[i][j]=max(Max2[i][j],min(Max1[i][j-1],Max1[fa[i][j-1]][j-1]));
    		}
    	}
    } 
    inline LL MAX(LL x,LL a,LL b,LL val)
    {
    	if(val>Max1[a][b]) return max(x,Max1[a][b]);
    	return max(x,Max2[a][b]);
    }
    inline LL Get(LL x,LL y,LL val)
    {
    	register LL i,res=0;
    	if(Depth[x]<Depth[y]) swap(x,y);
    	for(i=0;Depth[x]^Depth[y];++i) if((Depth[x]^Depth[y])&(1<<i)) res=MAX(res,x,i,val),x=fa[x][i];
    	if(!(x^y)) return res;
    	for(i=0;fa[x][i]^fa[y][i];++i);
    	for(;i>=0;--i) if(fa[x][i]^fa[y][i]) res=MAX(MAX(res,x,i,val),y,i,val),x=fa[x][i],y=fa[y][i];
    	return MAX(MAX(res,x,0,val),y,0,val); 
    }
    int main()
    {
    	register LL i,j,tot=1,ans=1e18,res=0,t;
    	for(read(n),read(m),i=1;i<=m;++i) read(e[i].from),read(e[i].to),read(e[i].val);
    	for(i=1;i<=n;++i) f[i]=i;
    	for(sort(e+1,e+m+1,cmp),i=1;i<=m&&tot<n;++i)
    	{
    		static LL fx,fy;
    		if((fx=getfa(e[i].from))^(fy=getfa(e[i].to))) f[fy]=fx,res+=e[i].val,++tot,e[i].used=1,v[e[i].from].push_back(i),v[e[i].to].push_back(i);
    	}	
    	for(Init(),i=1;i<=m;++i)
    		if(!e[i].used) t=Get(e[i].from,e[i].to,e[i].val),ans=min(ans,res+e[i].val-t);
    	return write(ans),fwrite(pp,1,pp_,stdout),0;
    }
    
  • 相关阅读:
    mock.js 模拟数据
    pa
    观察者模式
    WebSocket
    Nginx官方文档学习
    Java中文乱码解决
    Jersey+Spring+Maven(转)
    App架构经验总结(转)
    JSONP跨域的原理解析(转)
    mongoDB学习
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/UniqueMST.html
Copyright © 2020-2023  润新知