题解 P1967 【货车运输】

· · 题解

终于把这题切了。。百感交集啊
听说 NOIp 前发题解可以 rp++,于是就有了这篇题解

首先我们来分析:
要使得货车装最重的货物,那么就必须货车走的路载重要尽可能大 —— 也就是说,载重较小的路是不会被走过的。

于是我们就可以想到构建一棵原图的最大生成树(参见模板 P3366 最小生成树)。这样就可以在满足各城市之间连通情况不变的前提下,使得城市之间路载重最大。

然后:求货车能装最大货物重量的问题,就转化成了求树上两点权值最小的边的问题了!

如果用朴素算法求的话,显然是不行的,单次查询会被卡到 \mathcal O(n) 的复杂度。
那我们可以换一种思路:倍增求出 u,v 两点的 LCA,设其为 t
那么 uv 的最小边权就是:ut 的最小边权 和 vt 的最小边权取较小值。

我们可以建一个数组 `minw`(即 Minimum Weight),其中 `minw[i][j]` 表示 $i$ 节点到其 $2^j$ 级祖先之间的最小边权。是不是和倍增 LCA 的做法很像? 这样就可以在求 LCA 的过程中得到答案了。 我们也容易推出 `minw` 的递推公式: (为了方便,这里设 $i$ 的 $2^j$ 级祖先为 $g$) ```cpp minw[i][j] = min(minw[i][j-1],minw[g][j-1]); ``` 有了递推公式,`minw` 数组就可以在预处理节点祖先信息时搞定了。 **** 最后就是计算答案的部分: 对于不连通的两个点很好判断,用并查集判一下,如果不连通直接输出 $-1$。 如果两点连通: 我们需要记录答案 `ans` ,并初始化为正无穷(取一个足够大的整数即可)。 对于点 $u$,倍增过程中每次跳到其 $2^j$ 级节点时,进行操作: ```cpp ans = min(ans,minw[u][j]); ``` 这样你就得到了答案! 注意坑点: 给定的图可能有一些互不连通的城市群,在预处理的时候不要遗漏了。 参考代码: ```cpp #include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<vector> #define N 10003 #define inf 0x3f3f3f3f using namespace std; struct edge{ int u,v,w; edge(int u=0,int v=0,int w=0):u(u),v(v),w(w){} bool operator < (const edge& a) const{ return w>a.w; } }; int f[N][15],depth[N],lg2[N]; int ft[N],minw[N][15]; bool vis[N]; vector<edge> adj[N]; edge e[50003]; int n,m; inline int min(int a,int b){ if(a>b) return b; return a; } int find(int x){ if(ft[x]==x) return x; ft[x] = find(ft[x]); return ft[x]; } inline void read(int &x){ x = 0; char c = getchar(); while(!isdigit(c)) c = getchar(); while(isdigit(c)){ x = (x<<3)+(x<<1)+c-'0'; c = getchar(); } } inline int getw(int u,int v){ int l = adj[u].size(); for(int i=0;i<l;++i){ if(v==adj[u][i].v) return adj[u][i].w; } } void dfs(int u,int fa){ vis[u] = true; depth[u] = depth[fa]+1; f[u][0] = fa; int w,g,v,l,t = lg2[depth[u]]; w = getw(u,fa); minw[u][0] = w; if(fa==0) minw[u][0] = inf; for(int i=1;i<t;++i){ g = f[u][i-1]; minw[u][i] = min(minw[u][i-1],minw[g][i-1]); f[u][i] = f[g][i-1]; } l = adj[u].size(); for(int i=0;i<l;++i){ v = adj[u][i].v; if(v==fa) continue; dfs(v,u); } } int lca(int a,int b){ if(find(a)^find(b)) return -1; int t,res = inf; if(depth[a]<depth[b]){ t = a; a = b; b = t; } while(depth[a]>depth[b]){ t = lg2[depth[a]-depth[b]]-1; res = min(res,minw[a][t]); a = f[a][t]; } if(a==b) return res; for(t=lg2[depth[a]]-1;t>=0;--t){ if(f[a][t]==f[b][t]) continue; res = min(res,min(minw[a][t],minw[b][t])); a = f[a][t]; b = f[b][t]; } res = min(res,min(minw[a][0],minw[b][0])); return res; } void kruskal(){ sort(e+1,e+1+m); int cnt = n-1; for(int i=1;i<=m;++i){ if(cnt==0) break; int fu,fv,u,v,w; u = e[i].u; v = e[i].v; w = e[i].w; fu = find(u); fv = find(v); if(fu==fv) continue; ft[fu] = fv; --cnt; adj[u].push_back(edge(u,v,w)); adj[v].push_back(edge(v,u,w)); } } int main(){ memset(minw,inf,sizeof(minw)); int u,v,w,q; read(n),read(m); for(int i=1;i<=n;++i) ft[i] = i; for(int i=1;i<=m;++i){ read(u),read(v),read(w); e[i] = edge(u,v,w); } kruskal(); lg2[1] = 1; for(int i=2;i<=n;++i) lg2[i] = lg2[i-1]+(i>>lg2[i-1]==1); for(int i=1;i<=n;++i){ if(vis[i]) continue; dfs(i,0); } read(q); ++q; while(--q){ read(u),read(v); printf("%d\n",lca(u,v)); } return 0; } ```