[bzoj1977] [BeiJing2010组队] 次小生成树 Tree

Description

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

(\(value(e)\) 表示边 \(e\) 的权值) 这下小 \(C\) 蒙了,他找到了你,希望你帮他解决这个问题。

Input

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

Output

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

Sample Input

5 6

1 2 1

1 3 2

2 4 3

3 5 4

3 4 3

4 5 6

Sample Output

11

HINT

数据中无向图无自环; \(50\%\) 的数据 \(N \leq 2 000\) ,\(M \leq 3 000\)\(80\%\) 的数据 \(N \leq 50 000\), \(M \leq 100 000\)\(100\%\) 的数据 \(N\leq 100 000\),\(M\leq 300 000\) ,边权值非负且不超过 \(10^9\)


题解

首先有一个我并不会证的结论,(严格)次小生成树一定是最小生成树加一条边再删一条边得到的

回忆非严格次小生成树的做法,先处理出最小生成树上任意两点之间的路径上的最大值,然后枚举添加每一条不在最小生成树上的边 \((u,v)\) ,删掉原树上 \(u\)\(v\) 路径上的最大值,然后把这条边加入就可以了。

严格最小生成树与它不同的一点是,我们找的 “原树上 \(u\)\(v\) 路径上的最大值” 必须小于 我们要加的边的边权。
于是用树上倍增 \(lca\) 来求树上每两点间路径上的最大值和次大值就好了。


代码

求次大值是个坑,每次我都有考虑不到的地方……一定要注意注意注意!
调了好久,我好弱啊……

#include<cstdio>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 100005;
typedef long long ll;

struct node{
	int v,len;
	node *next;
}pool[N*2],*h[N];
int cnt;
void addedge(int u,int v,int len){
	node *p=&pool[++cnt],*q=&pool[++cnt];
	p->v=v;p->next=h[u];h[u]=p;p->len=len;
	q->v=u;q->next=h[v];h[v]=q;q->len=len;
}
int n,m;
int f[N][20],fmax[N][20],gmax[N][20],dep[N];
void dfs(int u){
	int v;
	for(node *p=h[u];p;p=p->next)
		if(!dep[v=p->v]){
			dep[v]=dep[u]+1;
			f[v][0]=u; fmax[v][0]=p->len; gmax[v][0]=0;
			for(int j=1;j<20;j++){
				f[v][j]=f[f[v][j-1]][j-1];
				fmax[v][j]=max(fmax[v][j-1],fmax[f[v][j-1]][j-1]);
				if(fmax[v][j-1]==fmax[f[v][j-1]][j-1])
					gmax[v][j]=max(gmax[v][j-1],gmax[f[v][j-1]][j-1]);
				else {
					gmax[v][j]=min(fmax[v][j-1],fmax[f[v][j-1]][j-1]);
					gmax[v][j]=max(gmax[v][j],max(gmax[v][j-1],gmax[f[v][j-1]][j-1]));
				}
			}
			dfs(v);
		}
}
int m1,m2;
inline void cg(int x,int i){
	if(fmax[x][i]>m1) m2=max(m1,gmax[x][i]),m1=max(m1,fmax[x][i]);
	else if(fmax[x][i]<m1) m2=max(m2,fmax[x][i]);
	else m2=max(m2,gmax[x][i]);
}
void getMax(int x,int y){
	m1=m2=0;
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=19;i>=0;i--)
		if(dep[f[x][i]]>=dep[y])
			cg(x,i),x=f[x][i];
	if(x==y) return;
	for(int i=19;i>=0;i--)
		if(f[x][i]!=f[y][i]){
			cg(x,i); cg(y,i);
			x=f[x][i]; y=f[y][i];
		}
	cg(x,0); cg(y,0);
}

struct edge{
	int u,v,len,vis;
	bool operator < (const edge &b) const{ return len<b.len; }
}ed[N*3];
int fa[N];
inline int getfa(int x) { return x==fa[x] ? x : fa[x]=getfa(fa[x]); }
void unit(int x,int y){
	x=getfa(x); y=getfa(y);
	fa[x]=y;
}

int main()
{
	ll ans=0;
	scanf("%d%d",&n,&m);
	for(int i=0;i<m;i++) scanf("%d%d%d",&ed[i].u,&ed[i].v,&ed[i].len);
	
	sort(ed,ed+m);
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=0;i<m;i++)
		if(getfa(ed[i].u)!=getfa(ed[i].v)){
			ed[i].vis=1;
			ans+=ed[i].len;
			unit(ed[i].u,ed[i].v);
			addedge(ed[i].u,ed[i].v,ed[i].len);
		}
	dep[1]=1; dfs(1);
	
	int Mx=2*1e9;
	for(int i=0;i<m;i++)
		if(!ed[i].vis){
			getMax(ed[i].u,ed[i].v);
			if(m1==ed[i].len) Mx=min(Mx,ed[i].len-m2);
			else Mx=min(Mx,ed[i].len-m1);
		}
	printf("%lld\n",ans+Mx);
	
	return 0;
}
既然选择了远方,便只顾风雨兼程
posted @ 2018-10-23 20:34  秋千旁的蜂蝶~  阅读(105)  评论(0编辑  收藏  举报