【Ybt OJ】[数据结构 第5章] 倍增(LCA) [后半章]

45 篇文章 0 订阅
7 篇文章 0 订阅

「 「 数据结构 」 」 5 5 5章 倍增问题 ( ( ( 3 3 3 ) ) )
目录:

D.货车运输
E.运输计划
F.次小生成树

A . A. A. 例题 1 1 1 货车运输

在这里插入图片描述
在这里插入图片描述
洛谷 l i n k link link

分析:

可以发现 一些权值较小的边是不会被走过的 那就可以把这些边去掉 也就是建最大生成树
要得到两点之间最小边权的最大值 最大值就是最大生成树解决
最小边权 就在最大生成树上 L C A LCA LCA 得出的 m i n ( d i s x , d i s y ) min(dis_x,dis_y) min(disx,disy)就是了.

CODE:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
#include<bitset>
//#pragma GCC optimize(2)
#define reg register
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int Inf=0x7fffffff;
const int N=1e4+5;
int n,m,head[N],tot,dep[N],f[N],fa[N][21],dis[N][21];
bool vis[N];
struct node{
	int to,next,k;
}a[N<<3];
struct node2{
	int x,y,dis;
}edge[N<<3];
void add(int x,int y,int k)
{
	a[++tot]=(node){y,head[x],k};
	head[x]=tot;
}
bool cmp(node2 x,node2 y){return x.dis>y.dis;}  //大到小排序
int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
void kruskal()
{
	//大到小排序 就建出最大生成树
	for(int i=1;i<=m;i++)
		if(find(edge[i].x)!=find(edge[i].y))
		{
			f[find(edge[i].x)]=find(edge[i].y);
			add(edge[i].x,edge[i].y,edge[i].dis);
			add(edge[i].y,edge[i].x,edge[i].dis);
		}
	return;
}
void dfs(int x)
{
	vis[x]=1;
	for(int i=head[x];i;i=a[i].next)
	{
		int qwq=a[i].to;
		if(vis[qwq]) continue;
		dep[qwq]=dep[x]+1;
		fa[qwq][0]=x;
		dis[qwq][0]=a[i].k;
		dfs(qwq);
	}
}
int lca(int x,int y)
{
	if(find(x)!=find(y)) return -1;
	int ans=Inf;
	if(dep[y]>dep[x]) swap(x,y);
	for(int i=20;i>=0;i--)
		if(dep[fa[x][i]]>=dep[y])
		{
			ans=min(ans,dis[x][i]);
			x=fa[x][i];
		}
	if(x==y) return ans;
	for(int i=20;i>=0;i--)
		if(fa[x][i]!=fa[y][i])
		{
			ans=min(ans,min(dis[x][i],dis[y][i]));
			x=fa[x][i];
			y=fa[y][i];
		}
	ans=min(ans,min(dis[x][0],dis[y][0]));
	return ans;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int x,y,k;
		scanf("%d%d%d",&x,&y,&k);
		edge[i].x=x;
		edge[i].y=y;
		edge[i].dis=k;
	}
	sort(edge+1,edge+m+1,cmp);
	for(int i=1;i<=n;i++) f[i]=i;
	kruskal();
	for(int i=1;i<=n;i++)
		if(!vis[i])
		{
			dep[i]=1;
			dfs(i);
			fa[i][0]=i;
			dis[i][0]=Inf;
		}
	for(int i=1;i<=20;i++)
		for(int j=1;j<=n;j++)
		{
			fa[j][i]=fa[fa[j][i-1]][i-1];
			dis[j][i]=min(dis[j][i-1],dis[fa[j][i-1]][i-1]);
		}
	int T;
	scanf("%d",&T);
	while(T--)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		printf("%d\n",lca(x,y));
	}
	
	return 0;
} 

E . E. E. 例题 5 5 5 运输计划

在这里插入图片描述
在这里插入图片描述
洛谷 l i n k link link

分析:

求树上距离 就会想到 L C A LCA LCA
肯定要找长度最长的边用虫洞 但是这条边必须被所有路径经过
那就可以统计边的出现次数 出现次数 = = =路径数量 就选出长度最大一条 看最长路径 − - 最长边 < = <= <=要求时间

要求时间就是二分的 m i d mid mid r r r是最长路径 l l l最长路径 − - 最长边
统计边次数 相当于区间 + 1 +1 +1 那就可以差分解决

CODE:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
#include<bitset>
//#pragma GCC optimize(2)
#define reg register
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=3e5+5;
int n,m,head[N],tot,fa[N][21],dis[N][21],dep[N];
int ans,val[N],cnt,l,r,mid;
struct node{
	int to,next,x;
}edge[N<<1];
struct node2{
	int x,y,lca,dis;
}a[N];
bool cmp(node2 x,node2 y){return x.dis>y.dis;}
void add(int x,int y,int k)
{
	edge[++tot]=(node){y,head[x],k};
	head[x]=tot;
}
void dfs(int x,int father)
{
	fa[x][0]=father;
	dep[x]=dep[father]+1;
	for(int i=head[x];i;i=edge[i].next)
	{
		int qwq=edge[i].to;
		if(qwq!=father)
		{
			dfs(qwq,x);
			dis[qwq][0]=edge[i].x;
		}
	}
}
int LCA(int x,int y,int p)
{
	if(dep[y]>dep[x]) swap(x,y);
	for(int i=20;i>=0;i--)
		if(dep[fa[x][i]]>=dep[y])
		{
			a[p].dis+=dis[x][i];
			x=fa[x][i];
		}
	if(x==y) return x;
	for(int i=20;i>=0;i--)
		if(fa[x][i]!=fa[y][i])
		{
			a[p].dis+=dis[x][i]+dis[y][i];
			x=fa[x][i];
			y=fa[y][i];
		}
	a[p].dis+=dis[x][0]+dis[y][0];
	return fa[x][0];
}
void ret(int x,int father)
{
	for(int i=head[x];i;i=edge[i].next)
	{
		int qwq=edge[i].to;
		if(qwq!=father)
		{
			ret(qwq,x);
			val[x]+=val[qwq];
		}
	}
}
bool check(int x)
{
	cnt=0;
	memset(val,0,sizeof(val));
	for(int i=1;i<=m;i++)
	{
		if(a[i].dis<=x) break;
		val[a[i].lca]--;
		val[a[i].x]++;
		val[a[i].lca]--;
		val[a[i].y]++;
		cnt++;
	}
	ret(1,0);
	int maxn=0;
	for(int i=1;i<=n;i++)
		if(val[i]==cnt)
			maxn=max(maxn,dis[i][0]);  //选权值最大用虫洞
	return a[1].dis-maxn<=mid?1:0;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++)
	{
		int x,y,k;
		scanf("%d%d%d",&x,&y,&k);
		add(x,y,k);
		add(y,x,k);
		l=max(l,k);
	}
	dfs(1,0);
	for(int i=1;i<=20;i++)
		for(int j=1;j<=n;j++)
		{
			fa[j][i]=fa[fa[j][i-1]][i-1];
			dis[j][i]=dis[j][i-1]+dis[fa[j][i-1]][i-1];
		}
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&a[i].x,&a[i].y);
		a[i].lca=LCA(a[i].x,a[i].y,i);
		r=max(r,a[i].dis);
	}
	l=r-l;
	sort(a+1,a+m+1,cmp);
	while(l<=r)
	{
		mid=(l+r)>>1;
		if(check(mid))
		{	
			ans=mid;
			r=mid-1;
		}
		else l=mid+1;
	}
	printf("%d",ans);
	
	return 0;
	
} 

F . F. F. 例题 6 6 6 次小生成树

在这里插入图片描述
洛谷 l i n k link link

分析:

思路:倍增 L C A + K r u s k a l LCA+Kruskal LCA+Kruskal
K r u s k a l Kruskal Kruskal有被证明 任意 ( x , y ) (x,y) (x,y)之间的边权最大值 < = ( x , y ) <=(x,y) <=(x,y)之间未被选入边的边权 ( ( (毕竟 K r u s k a l Kruskal Kruskal是贪心求最小生成树 ) ) )

所以 不严格次小生成树 只要遍历每条不选边 ( x , y , k ) (x,y,k) (x,y,k) 替换 ( x , y ) (x,y) (x,y)最大边
但要求的是 严格次小生成树
不严格在哪 ? ? ? 小于等于 等于了就不是严格次小
所以还要找个次大边 L C A LCA LCA分别求 最大边和次大边 最后去替换

CODE:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
#include<bitset>
//#pragma GCC optimize(2)
#define reg register
#define Inf 2147483647000000
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=4e5+5,M=9e5+5;
struct node{
	ll x,y,k,next;
}a[M<<1],G[N<<1];
ll tot,head[N],fa[N][19],maxn[N][19],minn[N][19],dep[N],father[N],ans=Inf;
ll n,m;
bool vis[M<<1];
inline void add(ll x,ll y,ll k)
{
	G[++tot]=(node){x,y,k,head[x]};
	head[x]=tot;
}
inline void dfs(ll x,ll father)
{
	fa[x][0]=father;
	for(reg ll i=head[x];i;i=G[i].next)
	{
		ll qwq=G[i].y;
		if(qwq==father) continue;
		dep[qwq]=dep[x]+1ll;
		maxn[qwq][0]=G[i].k;
		minn[qwq][0]=-Inf;
		dfs(qwq,x);
	}
}
inline bool cmp(node x,node y){return x.k<y.k;}
inline ll find(ll x){return x==father[x]?x:father[x]=find(father[x]);}
inline ll LCA(ll x,ll y)
{
	if(dep[x]<dep[y]) swap(x,y);
	for(reg ll i=18;i>=0;i--)
	{ 
		if(dep[fa[x][i]]>=dep[y])
			x=fa[x][i];
	}
	if(x==y) return x;
	for(reg ll i=18;i>=0;i--)
		if(fa[x][i]!=fa[y][i])
		{
			x=fa[x][i];
			y=fa[y][i];
		}
	return fa[x][0];
}
inline ll query(ll x,ll y,ll val)
{
	ll res=-Inf;
	for(reg ll i=18;i>=0;i--)
	{
		if(dep[fa[x][i]]>=dep[y])
		{
			if(val!=maxn[x][i]) res=max(res,maxn[x][i]);
			else res=max(res,minn[x][i]);
			x=fa[x][i];
		}
	}
	return res;
}
int main()
{
	scanf("%lld%lld",&n,&m);
	for(ll i=1;i<=m;i++)
		scanf("%lld%lld%lld",&a[i].x,&a[i].y,&a[i].k);
	sort(a+1,a+m+1,cmp);
	for(reg ll i=1;i<=n;i++)
		father[i]=i;
	ll cnt=0ll;
	for(reg ll i=1;i<=m;i++)
	{
		ll fx=find(a[i].x),fy=find(a[i].y);
		if(fx!=fy)
		{
			cnt+=a[i].k;
			father[fx]=fy;
			add(a[i].x,a[i].y,a[i].k);
			add(a[i].y,a[i].x,a[i].k);
			vis[i]=1;
		}
	}	
	minn[1][0]=-Inf;
	dep[1]=1ll;
	dfs(1,-1);
	
	for(reg ll i=1;i<=18;i++)
		for(reg ll j=1;j<=n;j++)
		{
			fa[j][i]=fa[fa[j][i-1]][i-1];
			maxn[j][i]=max(maxn[j][i-1],maxn[fa[j][i-1]][i-1]);  //最大
			minn[j][i]=max(minn[j][i-1],minn[fa[j][i-1]][i-1]);  //次大
			if(maxn[j][i-1]>maxn[fa[j][i-1]][i-1]) 
				minn[j][i]=max(minn[j][i],maxn[fa[j][i-1]][i-1]);
			else if(maxn[j][i-1]<maxn[fa[j][i-1]][i-1])
				minn[j][i]=max(minn[j][i],maxn[j][i-1]);
		}
	for(reg ll i=1;i<=m;i++)
	{
		if(!vis[i])
		{
			ll lca=LCA(a[i].x,a[i].y);
			ll maxx=query(a[i].x,lca,a[i].k);
			ll maxy=query(a[i].y,lca,a[i].k);
			ans=min(ans,cnt-max(maxx,maxy)+a[i].k); //替换
		}
	}
	printf("%lld\n",ans);
	
	return 0;
	
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值