次小生成树(严格次小)(模版)

这道题调了快2h,因为一个极其愚蠢的错误

算法要素:kruskal+倍增LCA+同步求最大值次大值

算法分析:

基本思路
首先肯定要生成一个最小生成树,然后有两种枚举方式:
<1>可以选择枚举生成树上每一条边
<2>枚举每一条非树边

为什么要枚举呢?显然是因为这个问题并没有明显的单调性,无法通过贪心或者二分来排除一些无用的情况。

那么我们还要面临最后一个问题:次小生成树分为严格次小生成树和非严格次小生成树。这个问题稍后再讨论。

具体实现
先考虑如何枚举树上的每一条边:
(1)最短路做法:删去一条树枝边,用djst或者spfa求出两点之间不经过原树枝边的最短路径。若要求严格次小,求严格次短路。若不要求严格次小,则直接求最短路即可。
tips:复杂度O(nmlogm),不够优秀,只能过部分分。
(2)我想不出其他的了qwq

考虑如何枚举非树边来代替树边:
考虑将一条非树边加入生成树中,这一过程肯定导致了有且仅有一条原树枝边可以被从生成树中删去。
为了使生成树次小,那么要删去的一定是当前可删去的边中最大的一条。
经过画图就可以发现,可以删去的边在非树边两端点和二者LCA所形成的环上。(图片稍后补充)
因此只需找到这个环上的最大边,统计答案即可。
由于倍增LCA使用了st表的预处理方法,而此方法也可用于统计最大值和次大值,故可以同步进行。

最后理顺一下算法思路:

step1:求出kruskal,建立最小生成树
step2:倍增LCA,同时预处理最大值和次大值(若不要求严格次小,则只预处理最大值即可)
step3:枚举每条非树边,求出环上的最大值,统计答案,求出最小值。
最终复杂度O(2mlongm+n+18n+m)----》O(mlogm+19n+m)十分优秀,过1e5的数据点基本没什么问题

tips:需要注意的问题我调题时犯的愚蠢错误
(1)要给次小值赋一个极小值的初值
(2)最后枚举的是非树边而非树边
(3)建最小生成树的时候需要加双向边,因为需要在树上求LCA,不加双向边会挂到20分
说实话就算是根据树的定义也应该加双向边呀啊喂,我为什么会被这个问题卡了快两个小时a。。

那么就来几道模板题吧:

-洛谷P4180 [BJWC2010]严格次小生成树
loj#10068. 「一本通 3.1 练习 3」秘密的牛奶运输
loj#10133. 「一本通 4.4 例 4」次小生成树

唯一不足之处就是我找到的这三道题完全一样。。。。只是题面背景不一样而已。。

Code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e5+50;
const ll INF=2e18;
ll n,m,ecnt=-1,tot;
ll fa[maxn],head[maxn];
ll dep[maxn],anc[maxn][25];
ll ancmax[maxn][25],ancmin[maxn][25];
ll totans=INF,sum=0;
bool vis[maxn];
struct lena
{
    ll u,v,w;
}edge[maxn<<3];
struct mint
{
    ll nxt,u,v,w;
}e[maxn<<1];
bool cmp(lena a,lena b)
{
    return a.w<b.w;
}
inline void addline(ll u,ll v,ll w)
{
    e[++ecnt].nxt=head[u];
    e[ecnt].v=v;
    e[ecnt].u=u;
    e[ecnt].w=w;
    head[u]=ecnt;
}
ll Find(ll x)
{
    if(x==fa[x]) return x;
    return fa[x]=Find(fa[x]);
}
void dfs(ll x,ll fa)
{
    dep[x]=dep[fa]+1;
    anc[x][0]=fa;
    for(ll i=head[x];~i;i=e[i].nxt)
    {
        int v=e[i].v;
        if(v==fa) continue;
    	ancmax[v][0]=e[i].w;
    	ancmin[v][0]=-INF;
		dfs(v,x);
    }
}
void cal()
{
	for(ll i=1;i<=18;++i)
	{
		for(ll x=1;x<=n;++x)
		{
			anc[x][i]=anc[anc[x][i-1]][i-1];
			ancmax[x][i]=max(ancmax[x][i-1],ancmax[anc[x][i-1]][i-1]);
			ancmin[x][i]=max(ancmin[x][i-1],ancmin[anc[x][i-1]][i-1]);
			if(ancmax[x][i-1]>ancmax[anc[x][i-1]][i-1]) ancmin[x][i]=max(ancmin[x][i],ancmax[anc[x][i-1]][i-1]);
			else if(ancmax[x][i-1]<ancmax[anc[x][i-1]][i-1]) ancmin[x][i]=max(ancmin[x][i],ancmax[x][i-1]);	
		}
	}
}
ll LCA(ll x,ll y)
{   
    if(dep[x]<dep[y]) swap(x,y);
    for(ll i=18;i>=0;--i)
    {
        if(dep[anc[x][i]]>=dep[y]) x=anc[x][i];        
    } 
    if(x==y) return x;
    for(ll i=18;i>=0;--i)
    {
    	if(anc[x][i]!=anc[y][i])
    	{
    		x=anc[x][i];
			y=anc[y][i];		
    	}
    }
    return anc[x][0];
}
ll getmax(ll x,ll fath,ll maxx)
{
	ll ans=-INF;
	for(ll i=18;i>=0;--i)
	{
		if(dep[anc[x][i]]>=dep[fath])
		{
			if(ancmax[x][i]!=maxx) ans=max(ans,ancmax[x][i]);
			else ans=max(ans,ancmin[x][i]);
			x=anc[x][i];
		}
	}
	return ans;
}
int main()
{
	memset(head,-1,sizeof(head));
    scanf("%lld%lld",&n,&m);
    for(ll i=1;i<=m;++i)
    {
        ll x,y,z;
        scanf("%lld%lld%lld",&x,&y,&z);
        edge[i].u=x;
        edge[i].v=y;
        edge[i].w=z;
    }
    for(ll i=1;i<=n;++i) fa[i]=i;
    sort(edge+1,edge+m+1,cmp);
    for(ll i=1;i<=m;++i)
    {
        ll x=Find(edge[i].u),y=Find(edge[i].v);
        if(x!=y)
        {
			fa[x]=y;
        	addline(edge[i].u,edge[i].v,edge[i].w);
        	addline(edge[i].v,edge[i].u,edge[i].w);
        	sum+=edge[i].w;
        	tot++;
        	vis[i]=true;
        	if(tot==n-1) break;
    	}
	}
	ancmin[1][0]=-INF;
    dfs(1,0);
    cal();
    totans=INF;
    for(ll i=1;i<=m;++i)
    {
    	if(!vis[i])
    	{
			ll x=edge[i].u,y=edge[i].v;
			ll fath=LCA(x,y);
			ll maxx=max(getmax(x,fath,edge[i].w),getmax(y,fath,edge[i].w));
			totans=min(totans,sum-maxx+edge[i].w);
    	}
	}
    printf("%lld",totans);
    return 0;
}
posted @ 2021-10-26 08:19  Mint-hexagram  阅读(111)  评论(0编辑  收藏  举报