luogu P4180 【模板】严格次小生成树[BJWC2010]

思路:

首先要想清楚一个问题,对于次小生成树,肯定是在最小生成树上断掉一条边,然后在非最小生成树边中加一条边进去产生的(具体窝也不会证鸭;知道了这个非常显然的结论后具体思路就好想了.

做法:

我们考虑暴力删边和加边.对于每条未在最小生成树中的边,考虑删掉最小生成树中的一条边(边权尽可能大)并加入此边,因为非最小生成树的边权肯定都大于等于最小生成树边的边权,所以删掉的边边权越大,新树的总边权才会越小,并且要保证新树还是生成树.

所以我们先求出最小生成树记录下总边权值,然后对于每一条非最小生成树边的两个节点在最小生成树上求一次\(lca\)(保证新树是生成树),然后求出节点到\(lca\)的最大边权和次大边权(因为最大边权可能和要加进去的边边权相等不满足严格次小生成树,所以要多求一个次小边权).然后用总边权减去求得最大边权(或次大边权)再加上要加进去的边的边权,记录答案最小值即可.

// luogu-judger-enable-o2
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=100000+5;
const int M=300000+5;
const int logs=19;
const int INF=2147483647*1e9;
int n,m,tot,cnt,sum;
int head[N],dep[N],pre[N][logs+1],vis[M],fa[N],zd[N][logs+1],cd[N][logs+1];
struct Edge{
	int next,to,dis;
}e[M*2];
struct node{
	int u,v,w;
}a[M*2];
int find(int x){return x==fa[x] ? fa[x] : fa[x]=find(fa[x]);}
inline void merge(int x,int y){fa[x]=y;}
inline void add_edge(int from,int to,int dis){
	e[++tot].next=head[from];
	e[tot].to=to;
	e[tot].dis=dis;
	head[from]=tot;
}
bool cmp(node a,node b){return a.w<b.w;}
void dfs(int now,int faa){
	pre[now][0]=faa;
	dep[now]=dep[faa]+1;
	for(int i=1;i<=logs;i++){
		pre[now][i]=pre[pre[now][i-1]][i-1];
		zd[now][i]=max(zd[now][i-1],zd[pre[now][i-1]][i-1]);//最大值
		cd[now][i]=max(cd[now][i-1],cd[pre[now][i-1]][i-1]);//次大值
		if(zd[now][i-1]>zd[pre[now][i-1]][i-1]) cd[now][i]=max(cd[now][i],zd[pre[now][i-1]][i-1]);//这里次大值可能与两个最大值中较小的那个更新
		else if(zd[now][i-1]<zd[pre[now][i-1]][i-1]) cd[now][i]=max(cd[now][i],zd[now][i-1]);
	}//倍增处理祖先,最大值,次大值.
	for(int i=head[now];i;i=e[i].next)
		if(e[i].to!=faa) dfs(e[i].to,now);
}
void cx(int x,int faa){
	for(int i=head[x];i;i=e[i].next){
		int v=e[i].to;
		if(v==faa) continue;
		zd[v][0]=e[i].dis;
		cd[v][0]=-INF;
		cx(v,x);
	}
}//预处理最大值次大值数组
inline int lca(int x,int y,int w){
	int ans=-INF;
	if(dep[x]<dep[y]) std::swap(x,y);
	for(int i=logs;i>=0;i--)
		if(dep[pre[x][i]]>=dep[y]){
			if(zd[x][i]==w) ans=max(ans,cd[x][i]);
			else ans=max(ans,zd[x][i]);
			x=pre[x][i];
		}
	if(x==y) return ans;
	for(int i=logs;i>=0;i--)
		if(pre[x][i]!=pre[y][i]){
			if(w==zd[x][i]) ans=max(ans,cd[x][i]);
			else ans=max(ans,zd[x][i]);
			if(w==zd[y][i]) ans=max(ans,cd[y][i]);
			else ans=max(ans,zd[y][i]);
			x=pre[x][i],y=pre[y][i];
		}
	if(w==zd[x][0]) ans=max(ans,cd[x][0]);
	else ans=max(ans,zd[x][0]);
	if(w==zd[y][0]) ans=max(ans,cd[y][0]);
	else ans=max(ans,zd[y][0]);
	return ans;
}//这里直接在求lca的过程中把最大值和次大值求出来.
signed main(){
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;i++) scanf("%lld%lld%lld",&a[i].u,&a[i].v,&a[i].w);
	sort(a+1,a+1+m,cmp);
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++){
		int x=find(a[i].u),y=find(a[i].v);
		if(x!=y){
			++cnt;
			merge(x,y);
			sum+=a[i].w;
			add_edge(a[i].u,a[i].v,a[i].w);
			add_edge(a[i].v,a[i].u,a[i].w);
			vis[i]=1;//标记最小生成树边
			if(cnt==n-1) break;
		}
	}//最小生成树
	cx(1,0);
	dfs(1,0);//预处理
	int ans=INF;
	for(int i=1;i<=m;i++){
		if(!vis[i]){
			int k=lca(a[i].u,a[i].v,a[i].w);
			ans=min(ans,sum-k+a[i].w);
		}
	}
	printf("%lld",ans);
	return 0;
}
posted @ 2019-08-08 20:37  Junglove  阅读(100)  评论(0编辑  收藏  举报