最小生成树刷题记录

很巧妙的最小生成树:
一.CF1120D Power Tree
题目大意:题目
在这里插入图片描述
第一眼很像个树型dp,实际上树型dp也可做。但有一个巧妙地办法:考虑到叶子节点的dfn序有序,将叶子节点排列为序列,每个节点控制这个序列的某一个区间。题目转化为对区间进行修改,使得区间所有点值为0,求修改的最小代价和方案并集。

区间修改可以想到差分,对[l,r]修改实际上是差分数组c[l]+x,c[r+1]-x,考虑最终结果的差分数组一定是c[1…n]=0(这里n是叶子节点数),所有的代价都传递给了c[n+1],其实可以把差分数组的修改过程抽象为单向的代价传递,我们希望把c[l]修改为0,代价传递给了c[r+1],所以考虑l–>r+1建边,代价为节点的w,此时求最小代价便转化为了最小生成树,为什么你,只要区间的所有点相连,那么最终所有的代价都到了n+1身上,此时的差分数组满足条件。

另外需要注意的是答案求并集,在krus的时候,要考虑相同边权的边集,以边集为单位加入答案,并且连边和加答案要分开做,这里看代码很好懂。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<vector>
#define LL long long 
#define MAXN 200010
#define INF 0x3f3f3f3f
using namespace std;
struct edge{
	int u,v,len,pos;
}e[MAXN];
int n,m,c[MAXN],ldfn[MAXN],rdfn[MAXN],cnt,f[MAXN],ans[MAXN],tot;
LL all_len;
vector<int> E[MAXN];

void dfs(int u,int fa){
	if(E[u].size()==1&&u!=1){
		ldfn[u]=rdfn[u]=++cnt;
		e[++m].u=ldfn[u],e[m].v=rdfn[u]+1,e[m].len=c[u],e[m].pos=u;
		return;
	}
	ldfn[u]=INF,rdfn[u]=-1;
	for(int i=0;i<E[u].size();i++){
		int v=E[u][i];
		if(v==fa) continue;
		dfs(v,u);
		ldfn[u]=min(ldfn[u],ldfn[v]);
		rdfn[u]=max(rdfn[u],rdfn[v]);
	}
	e[++m].u=ldfn[u],e[m].v=rdfn[u]+1,e[m].len=c[u],e[m].pos=u;
}
bool cmp(edge a,edge b){return a.len<b.len;}
int findfa(int u){return f[u]==u?u:f[u]=findfa(f[u]);}
void krus(){
	sort(e+1,e+m+1,cmp);
	for(int i=1;i<=n;i++) f[i]=i;
	for(int i=1;i<=m;){
		int t=i;
		while(e[t].len==e[t+1].len&&t+1<=m) t++;
		for(int j=i;j<=t;j++){
			if(findfa(e[j].u)!=findfa(e[j].v)){
				tot++;
				ans[e[j].pos]=1;
			}
		}
		for(int j=i;j<=t;j++){
			int fu=findfa(e[j].u),fv=findfa(e[j].v);
			if(fu!=fv){
				f[fu]=fv;
				all_len+=e[j].len;
			}
		}
		i=t+1;
	}
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>c[i];
	for(int i=1;i<n;i++){
		int u,v; cin>>u>>v;
		E[u].push_back(v);
		E[v].push_back(u);
	}
	dfs(1,0);
	krus();
	cout<<all_len<<' '<<tot<<endl;
	for(int i=1;i<=n;i++) if(ans[i]) cout<<i<<' ';
	return 0;
}

二.P3623 [APIO2008] 免费道路
题目

很巧妙的最小生成树问题。

题目大意:
在这里插入图片描述
鹅卵石路边权为1,反之为0。
先跑一遍正常的最小生成树,这时被加进去为1的边一定是必要的边,证明:若跑完所有0边仍没生成树,则后面加进去的1边一定是唯一的桥,没有此桥一定连不通。

记录这些必要的边,如果 边数>k了,无解。

第二遍krus,先连必要的边,然后再补齐差的1边,即按边权从大到小跑krus,因为只要求一组解,那么差的边只要合法可以任选,可以证明一定能成树。

此题结束。

#include<bits/stdc++.h>
#define MAXN 20010
#define MAXM 100010
using namespace std;
int n,m,k,f[MAXN],flag=1,must[MAXM],ans[MAXN];
struct edge{
	int u,v,len;
}e[MAXM];
bool cmp(edge a,edge b){return a.len<b.len;}
int findfa(int u){return f[u]==u?u:f[u]=findfa(f[u]);}
void krus(){
	for(int i=1;i<=n;i++) f[i]=i;
	int cnt=0,cnt1=0;
	for(int i=1;i<=m;i++){
		int fu=findfa(e[i].u),fv=findfa(e[i].v);
		if(fu!=fv){
			f[fu]=fv;
			if(e[i].len) must[i]=1,cnt1++;
			cnt++;
		}
		if(cnt==n-1) break;
	}
	if(cnt1>k) flag=0;
}
void ag_krus(){
	for(int i=1;i<=n;i++) f[i]=i;
	int cnt1=0;
	for(int i=1;i<=m;i++){
		if(must[i]){
			f[findfa(e[i].u)]=findfa(e[i].v);
			cnt1++;
			ans[++ans[0]]=i;
		}
	}
	for(int i=m;i>0;i--){
		if(must[i]) continue;
		int fu=findfa(e[i].u),fv=findfa(e[i].v);
		if(e[i].len&&cnt1<k&&fu!=fv) f[fu]=fv,cnt1++,ans[++ans[0]]=i;
		if(!e[i].len&&cnt1==k&&fu!=fv) f[fu]=fv,ans[++ans[0]]=i;
		if(ans[0]==n-1) break;
	}
	if(cnt1<k) flag=0;
}
int main(){
	cin>>n>>m>>k;
	for(int i=1;i<=m;i++){
		int u,v,w; cin>>u>>v>>w;
		e[i].u=u,e[i].v=v,e[i].len=w?0:1;
	}
	sort(e+1,e+m+1,cmp);
	krus();
	if(!flag){
		puts("no solution");
		return 0;
	}
	ag_krus();
	if(!flag){
		puts("no solution");
		return 0;
	}
	else for(int i=1;i<=ans[0];i++) cout<<e[ans[i]].u<<' '<<e[ans[i]].v<<' '<<!e[ans[i]].len<<endl;
	return 0;
}

三.P4180 [BJWC2010] 严格次小生成树
题目
在这里插入图片描述
思路非常好想,找到一条不在最小生成树中的边,这时加到最小生成树中,此时有环了,在这个环中找到次大的那条边删掉,为什么要次大,因为是严格次小生成树,如果环中有与这个加入边值相等的边,那么我们需要次大的那一个,所以要维护一条链上的最大值和次大值,考虑倍增,求LCA,同时维护链上的最大和次大值即可。

#include<bits/stdc++.h>
#define LL long long
#define MAXN 100010
#define INF 0x7fffffff
using namespace std;
struct edge{
	int u,v,len;
}e[MAXN*3];
struct Edge{
	int to,len;
};
vector<Edge> E[MAXN];
int n,m,fa[MAXN],vis[MAXN*3];
LL all_len;
int f[MAXN][22],g[MAXN][22],h[MAXN][22],dep[MAXN];
bool cmp(edge a,edge b){return a.len<b.len;}
int findfa(int u){return fa[u]==u?u:fa[u]=findfa(fa[u]);}
void krus(){
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++){
		int fu=findfa(e[i].u),fv=findfa(e[i].v);
		if(fu!=fv){
			fa[fu]=fv,vis[i]=1;
			E[e[i].u].push_back((Edge){e[i].v,e[i].len});
			E[e[i].v].push_back((Edge){e[i].u,e[i].len});
			all_len+=e[i].len;
		}
	}
}
void dfs(int u,int ft,int l){
	dep[u]=dep[ft]+1,f[u][0]=ft,g[u][0]=l,h[u][0]=-INF;
	for(int i=1;i<=20;i++){
		f[u][i]=f[f[u][i-1]][i-1];
		g[u][i]=max(g[u][i-1],g[f[u][i-1]][i-1]);
		h[u][i]=max(h[u][i-1],h[f[u][i-1]][i-1]);
		if(g[u][i-1]>g[f[u][i-1]][i-1]) h[u][i]=max(h[u][i],g[f[u][i-1]][i-1]);
		if(g[u][i-1]<g[f[u][i-1]][i-1]) h[u][i]=max(h[u][i],g[u][i-1]);
	}
	for(int i=0;i<E[u].size();i++){
		int v=E[u][i].to;
		if(v==ft) continue;
		dfs(v,u,E[u][i].len);
	}
}
int LCA(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=20;i>=0;i--) if(dep[f[u][i]]>=dep[v]) u=f[u][i]; 
	if(u==v) return v;
	for(int i=20;i>=0;i--) if(f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i];
	return f[u][0];
}
int ask(int u,int v,int maxx){
	int ans=-INF;
	for(int i=20;i>=0;i--){
		if(dep[f[v][i]]>=dep[u]){
			if(maxx!=g[v][i]) ans=max(ans,g[v][i]);
			else ans=max(ans,h[v][i]);
			v=f[v][i];
		}
	}
	return ans;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++) cin>>e[i].u>>e[i].v>>e[i].len;
	sort(e+1,e+m+1,cmp);
	krus();
	dfs(1,0,0);
	LL ans=(1ll<<62);
	for(int i=1;i<=m;i++){
		if(vis[i]) continue;
		int lca=LCA(e[i].u,e[i].v);
		int max1=ask(lca,e[i].u,e[i].len),max2=ask(lca,e[i].v,e[i].len);
		ans=min(ans,all_len-max(max1,max2)+e[i].len);
	}
	cout<<ans<<endl;
	return 0;
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值