【洛谷1967】货车运输(最大生成树+倍增LCA)

本文介绍了一种解决最大载重路径问题的算法,通过构建最大生成树并利用倍增LCA算法,高效求解任意两点间最大载重路径。适用于城市道路网络分析,提升物流配送效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

点此看题面

大致题意: n n n个城市和 m m m条道路,每条道路有一个限重。多组询问,每次询问从 x x x y y y的最大载重为多少。


一个贪心的想法

首先,让我们来贪心一波。

由于要求最大载重,显然要让最小限重尽量大

不难发现,想要让最小限重尽量大,所经过的路径一定都在原图的最大生成树上。

于是,我们就可以用求最大生成树的方法来将原图转化为一棵树。

这样一来,原题就转化成了求树上两点之间的最小边权值

这应该是可以直接用倍增 L C A LCA LCA 来搞的吧。

L i n k Link Link

倍增 L C A LCA LCA 详见博客倍增LCA


如何用倍增 L C A LCA LCA求树上两点之间的最小边权值

不难想到,我们可以用 M i n i , j Min_{i,j} Mini,j来表示 i i i f a i , j fa_{i,j} fai,j 所经过的路径上的最小边权值。

因此,我们只需在 L C A LCA LCA的过程中每一次更新节点为当前节点祖先时,加上更新最小值的步骤即可,代码还是很简洁的。


代码
#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define uint unsigned int
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define abs(x) ((x)<0?-(x):(x))
#define Fsize 100000
#define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
#define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,FoutSize,stdout),Fout[(FoutSize=0)++]=ch))
#define N 10000
#define M 50000
#define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].val=z)
int FoutSize=0,OutputTop=0;char Fin[Fsize],*FinNow=Fin,*FinEnd=Fin,Fout[Fsize],OutputStack[Fsize];
using namespace std;
int n,m,ee=0,fa[N+5],lnk[N+5]={0},Depth[N+5],vis[N+5]={0},f[N+5][20],Min[N+5][20];
struct line//题目中给定图的边
{
    int x,y,v;
}w[M+5];
struct edge//原图最大生成树的边
{
    int to,nxt,val;
}e[2*N+5];
inline void read(int &x)
{
    x=0;static char ch;
    while(!isdigit(ch=tc()));
    while(x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
}
inline void write(int x)
{
    if(!x) return (void)pc('0');
    while(x) OutputStack[++OutputTop]=x%10+48,x/=10;
    while(OutputTop) pc(OutputStack[OutputTop]),--OutputTop;
}
inline bool cmp(line x,line y)//将原图给定的边排序
{
    return x.v>y.v;
}
inline int getfa(int x)//并查集函数
{
    return fa[x]^x?fa[x]=getfa(fa[x]):x;
}
inline void dfs(int x)//dfs初始化
{
    register int i;
    for(i=1;i<20;++i) f[x][i]=f[f[x][i-1]][i-1],Min[x][i]=min(Min[x][i-1],Min[f[x][i-1]][i-1]);//计算出f数组和Min数组
    for(vis[x]=1,i=lnk[x];i;i=e[i].nxt)//枚举当前节点的相邻节点
    {
        if(!(e[i].to^f[x][0])) continue;//如果枚举到的节点是当前节点的父亲节点,就跳过
        Depth[e[i].to]=Depth[f[e[i].to][0]=x]+1,Min[e[i].to][0]=e[i].val,dfs(e[i].to);//初始化子节点信息,并dfs该子节点
    }
}
inline int Query(int x,int y)//询问操作,类似于求LCA的过程
{
    register int i,res=1e9;//res记录答案
    if(Depth[x]<Depth[y]) swap(x,y);
    for(i=0;Depth[x]^Depth[y];++i) if((Depth[x]-Depth[y])&(1<<i)) res=min(res,Min[x][i]),x=f[x][i];//在更新当前节点为当前节点祖先的同时,也要更新最小值
    if(!(x^y)) return res;//如果x与y已经相等,就返回res
    for(i=0;f[x][i]^f[y][i];++i);
    for(--i;i>=0;--i) if(f[x][i]^f[y][i]) res=min(res,min(Min[x][i],Min[y][i])),x=f[x][i],y=f[y][i];//在更新当前节点为当前节点祖先的同时,也要更新最小值
    return min(res,min(Min[x][0],Min[y][0]));//返回x,y到父亲的边权与res中的较小值
}
int main()
{
    register int i,Q,x,y,fx,fy;
    for(read(n),read(m),i=1;i<=m;++i) read(w[i].x),read(w[i].y),read(w[i].v);//读入原图
    for(sort(w+1,w+m+1,cmp),i=1;i<=n;++i) fa[i]=i;//初始化并查集
    for(i=1;i<=m;++i) if((fx=getfa(w[i].x))^(fy=getfa(w[i].y))) fa[fx]=fy,add(w[i].x,w[i].y,w[i].v),add(w[i].y,w[i].x,w[i].v);//Kruskal最大生成树
    for(read(Q);Q;--Q)//对于每个询问进行操作
    {
        read(x),read(y);
        if(getfa(x)^getfa(y)) {pc('-'),pc('1'),pc('\n');continue;}//如果不在同一个联通块内,直接输出-1
        if(!vis[x]||!vis[y]) dfs(x);//如果这个联通块未访问过,就dfs遍历一遍初始化
        write(Query(x,y)),pc('\n');//输出两点之间的最小边权
    }
    return fwrite(Fout,1,FoutSize,stdout),0;
}
### LCA 最近公共祖先问题在树结构中使用倍增法的解题方法 #### 倍增算法简介 倍增算法是一种高效的技术,在路径查询、区间问题以及树的祖先查询等方面有着广泛应用。这种算法特别适合用于解决涉及多个层次关系的问题,比如最近公共祖先(LCA)问题[^1]。 #### LCA 定义及其重要性 在有根树中,对于两个节点 \(u\) 和 \(v\)LCA 是所有公共祖先中最深的那个节点。这个概念不仅限于普通的父子关系,还包括节点本身作为自己祖先的情况。因此,在任何给定的一对节点间都存在唯一的LCA[^2]。 #### 使用倍增法LCA的具体过程 为了提高查找效率,通常会对树进行预处理。具体来说,就是利用二进制表示来记录每个节点向上跳转\(2^k\)步后的父节点位置。这样做的好处是可以快速定位到目标节点之间的共同祖先,从而大大减少了实际计算量。以下是基于此思想的一种常见实现方式: ```cpp const int MAXN = 1e5 + 7; int fa[MAXN][20]; // 存储第i个结点往上走2^j步到达的父亲编号 int dep[MAXN]; // 结点深度数组 void dfs(int u,int father){ fa[u][0]=father; // 初始化fa[i][0],即直接父亲 dep[u]=dep[father]+1; for (auto &v : adj[u]) { if(v==father) continue; dfs(v,u); } } // 预处理f表 for(int j=1;(1<<j)<n;j++) for(int i=1;i<=n;i++) if(fa[i][j-1]!=-1) fa[i][j]=fa[fa[i][j-1]][j-1]; pair<int,int> getlca(int a,int b){ while(a!=b){ if(dep[a]<dep[b]) swap(a,b); int k=log2(dep[a]-dep[b]); a=fa[a][k]; } return {a,dep[a]}; } ``` 上述代码片段展示了如何构建倍增表格`fa[][]`并通过深度优先搜索初始化各个节点的信息。之后通过简单的循环操作即可完成任意两点之间LCA的查询工作[^4]。 #### 应用场景扩展 除了基本的LCA查询外,倍增法还可以与其他高级数据结构相结合,如并查集和树链剖分等,进一步优化复杂度较高的题目解答方案。此外,在动态规划领域也经常能看到它的身影,尤其是在那些涉及到子树范围内属性汇总或者状态转移方向确定等问题上[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值