LOJ #10133. 「一本通 4.4 例 4」次小生成树

题目描述

原题来自:BeiJing 2010 组队赛

给定一张 NNN 个点 MMM 条边的无向图,求无向图的严格次小生成树。

设最小生成树的边权之和为 sum\text{sum}sum,严格次小生成树就是指边权之和大于 sum\text{sum}sum 的生成树中最小的一个。

输入格式

第一行包含两个整数 NNN 和 MMM,表示无向图的点数与边数;

接下来 MMM 行,每行三个数 x,y,zx,y ,zx,y,z,表示点 xxx 和点 yyy 之间有一条边,边的权值为 zzz。

输出格式

包含一行,仅一个数,表示严格次小生成树的边权和。

数据保证必定存在严格次小生成树。

样例

样例输入

5 6 
1 2 1 
1 3 2 
2 4 3 
3 5 4 
3 4 3 
4 5 6

样例输出

11

数据范围与提示

对于全部数据,1≤N≤105,1≤M≤3×1051\le N\le 10^5,1\le M\le 3\times 10^51≤N≤105,1≤M≤3×105,数据中无向图无自环,边权值非负且不超过 10910^9109。

 

懂最小生成树原理的人这题很好写,可以去网易公开课上看一下算法导论麻省理工的视频

先跑一遍最小生成树

然后再加入一条边,可以发现图中一定出现了环,

再删除其中原有的最大一条边就是答案之一了,用LCA维护即可

如果没有严格,那这题就KO了

如果有严格呢?

再记下次小的边

如果最小的边与加入的边权值一样就删去次小边

反之就删去最小边

细节自己想(我不会告诉你我一遍过的

#include<cstdio>
#include<algorithm>
#include<iostream>
#define ll long long
using namespace std;
inline int read()
{
	int ret=0;
	char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9')
		ret=(ret<<1)+(ret<<3)+ch-'0',
		ch=getchar();
	return ret;
}

int n,m,cnt,num,t1,t2,ans;
ll s;
const int N=1e6+5;
int ff[N],he[N],to[N],nxt[N],w[N],dep[N];
int lg[N],f[N][21],d1[N][21],d2[N][21];
bool fl[N];
struct NA{
	int u,v,w;
}e[N];

bool cmp(NA i,NA j)
{
	return i.w<j.w;
}

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

inline void add(int u,int v,int k)
{
	to[++cnt]=v;
	nxt[cnt]=he[u];
	w[cnt]=k;
	he[u]=cnt;
}

void dfs(int fa,int u)
{
	dep[u]=(!fa)?0:dep[fa]+1;
	f[u][0]=fa;
	for(int i=1;i<=lg[dep[u]];i++)
	{
		f[u][i]=f[f[u][i-1]][i-1];
		d1[u][i]=max(d1[u][i-1],d1[f[u][i-1]][i-1]);
		d2[u][i]=max(d2[u][i-1],d2[f[u][i-1]][i-1]);
		if(d1[u][i-1]!=d1[u][i]) 
			d2[u][i]=max(d2[u][i],d1[u][i-1]);
		if(d1[f[u][i-1]][i-1]!=d1[u][i])
			d2[u][i]=max(d2[u][i],d1[f[u][i-1]][i-1]);
	}
	for(int e=he[u];e;e=nxt[e])
	{
		int v=to[e];
		if(v!=fa)
			d1[v][0]=w[e],
			dfs(u,v);
	}
}

void LCA(int u,int v)
{
	if(dep[u]>dep[v]) swap(u,v);
	t1=t2=0;
	while(dep[u]<dep[v])
	{
		if(t1<d1[v][lg[dep[v]-dep[u]]])
			t2=t1,t1=d1[v][lg[dep[v]-dep[u]]];
		else if(t1!=d1[v][lg[dep[v]-dep[u]]])
			t2=max(t2,d1[v][lg[dep[v]-dep[u]]]);
		else t2=max(t2,d2[v][lg[dep[v]-dep[u]]]);
		v=f[v][lg[dep[v]-dep[u]]];
	}
	for(int i=lg[dep[u]];i>=0;i--)
		if(f[u][i]!=f[v][i])
		{
			if(t1<d1[v][i])
				t2=t1,t1=d1[v][i];
			else if(t1!=d1[v][i])
				t2=max(t2,d1[v][i]);
			else t2=max(t2,d2[v][i]);
			v=f[v][i];
			if(t1<d1[u][i])
				t2=t1,t1=d1[u][i];
			else if(t1!=d1[u][i])
				t2=max(t2,d1[u][i]);
			else t2=max(t2,d2[u][i]);
			u=f[u][i];
		}
	if(u!=v) 
	{
		if(t1<d1[v][0])
			t2=t1,t1=d1[v][0];
		else if(t1!=d1[v][0])
			t2=max(t2,d1[v][0]);
		else t2=max(t2,d2[v][0]);
		if(t1<d1[u][0])
			t2=t1,t1=d1[u][0];
		else if(t1!=d1[u][0])
			t2=max(t2,d1[u][0]);
		else t2=max(t2,d2[u][0]);
	}
}
int main()
{
	n=read(),m=read();
	for(int i=1;i<=m;i++)
		e[i].u=read(),e[i].v=read(),e[i].w=read();
	sort(e+1,e+m+1,cmp);
	for(int i=1;i<=n;i++) ff[i]=i;
	num=0;
	for(int i=1;i<=m;i++)
	{
		int f1=find(e[i].u),f2=find(e[i].v);
		if(f1!=f2) 
		{
			s+=e[i].w;
			ff[f1]=f2;
			add(e[i].u,e[i].v,e[i].w);
			add(e[i].v,e[i].u,e[i].w);
			cnt++;
			fl[i]=1;
		}
		if(num==n-1) break;
	}
	lg[0]=-1;
	for(int i=1;i<=n;i++)
		lg[i]=lg[i>>1]+1;
	dfs(0,1);
	ans=2e9;
	for(int i=1;i<=m;i++)
		if(!fl[i])
			LCA(e[i].u,e[i].v),
			ans=min(ans,e[i].w-((e[i].w==t1)?t2:t1));
	printf("%lld\n",s+ans);
	return 0;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值