【BZOJ 1977】次小生成树

【题目】

传送门

题目描述:

C 最近学了很多最小生成树的算法,Prim 算法、Kurskal 算法、消圈算法等等。 正当小 C 洋洋得意之时,小 P 又来泼小 C 冷水了。小 P 说,让小 C 求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的,也就是说: 如果最小生成树选择的边集是 EM,严格次小生成树选择的边集是 ES,那么需要满足:

∑ e ∈ E M v a l u e ( e ) &lt; ∑ e ∈ E S v a l u e ( e ) \sum_{e\in E_M}value(e)&lt;\sum_{e\in E_S}value(e) eEMvalue(e)<eESvalue(e)

其中 v a l u e ( e ) value(e) value(e) 表示边 e e e 的权值。

这下小 C 蒙了,他找到了你,希望你帮他解决这个问题。

输入格式:

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

输出格式:

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

样例数据:

输入
5 6
1 2 1
1 3 2
2 4 3
3 5 4
3 4 3
4 5 6

输出
11

备注:

【数据范围】
数据中无向图无自环;
50 % 50\% 50% 的数据: n ≤ 2000 , m ≤ 3000 n≤2000,m≤3000 n2000,m3000
80 % 80\% 80% 的数据: n ≤ 50000 , m ≤ 100000 n≤50000,m≤100000 n50000,m100000
100 % 100\% 100% 的数据: n ≤ 100000 , m ≤ 300000 n≤100000,m≤300000 n100000,m300000
边权值非负且不超过 1 0 9 10^9 109


【分析】

次小生成树的模板题啦

大致讲一下算法步骤吧

次小生成树的话,和次短路的思路其实差不多

首先,肯定还是要求出最小生成树,那么现在就枚举不在最小生成树上的边,加上这条边之后,肯定就会形成一个环,将这个环上最长的边(除了新加的那条边)删掉之后,就是一个新的生成树,在新的生成树中选个最小的就行了

现在就考虑怎么实现了,就是用倍增

在最小生成树中,我们用 m a x 1 [ i ] [ j ] max1[i][j] max1[i][j] 表示从 i i i 往上跳 2 j 2^j 2j 步的最大值,用 m a x 2 [ i ] [ j ] max2[i][j] max2[i][j] 表示从 i i i 往上跳 2 j 2^j 2j 步的次大值(记录次大值是因为要求的是严格次小生成树,有时不能直接用最大值)

那么假设现在要新加 ( x , y ) (x,y) (x,y) 这条边,就找 x x x y y y 路径上的边权最(次)大值,直接倍增找就行了(和找 l c a lca lca 差不多)

找到之后,更新,统计最小值就行了,然后就做完了


【代码】

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
#define M 1000005
#define ll long long
#define inf 1ll<<60ll
using namespace std;
int n,m,t;
struct edge{int u,v,w;}e[M];
int first[N],v[M],w[M],nxt[M],vis[M];
int dep[N],father[N],fa[N][25],Max1[N][25],Max2[N][25];
bool comp(const edge &p,const edge &q)  {return p.w<q.w;}
int find(int x)  {return x==father[x]?x:father[x]=find(father[x]);}
void add(int x,int y,int z)  {nxt[++t]=first[x];first[x]=t;v[t]=y;w[t]=z;}
ll Kruskal()
{
	int i;ll ans=0;
	sort(e+1,e+m+1,comp);
	for(i=1;i<=m;++i)
	{
		int x=find(e[i].u);
		int y=find(e[i].v);
		if(x!=y)
		{
			vis[i]=1;
			father[x]=y,ans+=e[i].w;
			add(e[i].u,e[i].v,e[i].w);
			add(e[i].v,e[i].u,e[i].w);
		}
	}
	return ans;
}
void dfs(int x)
{
	int i;
	for(i=first[x];i;i=nxt[i])
	{
		if(v[i]==fa[x][0])  continue;
		fa[v[i]][0]=x,dep[v[i]]=dep[x]+1;
		Max1[v[i]][0]=w[i],Max2[v[i]][0]=0;
		dfs(v[i]);
	}
}
void init()
{
	int i,j;
	for(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]<Max1[i][j-1])  Max2[i][j]=Max1[i][j-1];
			if(Max1[i][j-1]>Max1[fa[i][j-1]][j-1]&&Max2[i][j]<Max1[fa[i][j-1]][j-1])  Max2[i][j]=Max1[fa[i][j-1]][j-1];
		}
	}
}
int find(int x,int y,int limit)
{
	int i,ans=0;
	if(dep[x]<dep[y])  swap(x,y);
	for(i=20;~i;--i)
	{
		if(dep[fa[x][i]]>=dep[y])
		{
			ans=max(ans,Max1[x][i]!=limit?Max1[x][i]:Max2[x][i]);
			x=fa[x][i];
		}
	}
	if(x==y)  return ans;
	for(i=20;~i;--i)
	{
		if(fa[x][i]!=fa[y][i])
		{
			ans=max(ans,Max1[x][i]!=limit?Max1[x][i]:Max2[x][i]),x=fa[x][i];
			ans=max(ans,Max1[y][i]!=limit?Max1[y][i]:Max2[y][i]),y=fa[y][i];
		}
	}
	ans=max(ans,Max1[x][0]!=limit?Max1[x][0]:Max2[x][0]);
	ans=max(ans,Max1[y][0]!=limit?Max1[y][0]:Max2[y][0]);
	return ans;
}
ll solve(ll MST)
{
	int i;ll ans=inf;
	for(i=1;i<=m;++i)
	{
		if(!vis[i])
		{
			int temp=find(e[i].u,e[i].v,e[i].w);
			if(temp!=e[i].w)  ans=min(ans,MST-temp+e[i].w);
		}
	}
	return ans;
}
int main()
{
	int i;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;++i)  father[i]=i;
	for(i=1;i<=m;++i)  scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
	ll MST=Kruskal();
	dep[1]=1,dfs(1);
	init();
	ll ans=solve(MST);
	printf("%lld",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值