P1967 货车运输 题解
简要题意:
给定一个无向图,若干组询问问 \(x \rightarrow y\) 所有路径上最小权值的最大值。
算法一
对于 \(60\%\) 的数据,\(1 \le n < 10^3,1 \le m < 5\times 10^4,1 \le q< 10^3\)
如果您有网络流板子的话,可以认为,本题是 给定网络图,若干组流量询问,直接 \(\text{ISAP}\) 乱跑就可以得到 \(O(qnm)\) 的优秀时间,轻松得到 \(60pts\) 及以上。(网络流算法时间跑不满)
说句闲话:这题网络流怎么优化
时间复杂度:\(O(qnm)\). 期望得分:\(60pts\). 实际得分 100pts
算法二
对于 \(100\%\) 的数据,\(1 \le n < 10^4 , 1 \le m < 5\times 10^4 , 1 \le q< 3\times 10^4 , 0 \le z \le 10^5\).
首先,我们考虑的是,如果,\(x \rightarrow u (w_1)\) , \(u \rightarrow v (w_2)\) ,与 \(x \rightarrow v (w_3)\) 同时存在的话,如果 \(\min(w_1,w_2) \leq w_3\),可以直接把 \(w_3\) 扔掉;走的时候肯定是走 \(x \rightarrow w \rightarrow v\) 了。
那么,我们得到了网络流的贪心算法 显然,我们需要对每一组 \(x \rightarrow y\) 都把这样的 废边 去掉。
嗯?所以我们要把所有 \(x \rightarrow y\) 的最大路径取出来,对吧?
那么,最大生成树 就映入我们眼帘:显然这是 最大生成树重构图 !
求最大生成树并只留下这个树上的边(重构),那么 \(x \rightarrow y\) 肯定是最大路径(不然肯定换边了)。
那么如何应对询问?显然,我们在 树上求两点路径 用 \(\text{LCA}\) 操作即可。
注意细节。
时间复杂度:\(O(n \log n + q \log n + m)\).
实际得分:\(100pts\).
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+1;
inline int read(){char ch=getchar(); int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
struct EE1 {int x,y,dis;} E1[N];
struct E2 {int to,nxt,w;} E2[N];
int head[N],n,m,f[N],deep[N],cnt,q;
int fa[N][21],w[N][21]; bool vis[N];
inline void add(int u,int v,int w) {
if(u==v) return;
E2[++cnt].nxt=head[u];
E2[cnt].to=v; E2[cnt].w=w;
head[u]=cnt; return;
}
inline int find(int x) {return f[x]==x?x:f[x]=find(f[x]);}
inline bool cmp(EE1 x,EE1 y) {return x.dis>y.dis;}
inline void kruskal() {
sort(E1+1,E1+1+m,cmp);
for(int i=1;i<=n;i++) f[i]=i;
for(int i=1;i<=m;i++) {
int u=E1[i].x,v=E1[i].y,w=E1[i].dis;
if(find(u)-find(v)) {
f[find(u)]=find(v);
add(u,v,w); add(v,u,w);
}
}
} //最大生成树模板
inline void dfs(int u) {
vis[u]=1;
for(int i=head[u];i;i=E2[i].nxt) {
int v=E2[i].to,t=E2[i].w;
if(vis[v]) continue;
deep[v]=deep[u]+1;
fa[v][0]=u; w[v][0]=t;
dfs(v);
} return;
} //预处理父亲
inline int LCA(int x,int y) {
if(find(x)-find(y)) return -1;
int ans=INT_MAX;
if(deep[x]>deep[y]) swap(x,y);
for(int i=20;i>=0;i--)
if(deep[fa[y][i]]>=deep[x]) ans=min(ans,w[y][i]),y=fa[y][i]; //调整深度
if(x==y) return ans;
for(int i=20;i>=0;i--)
if(fa[x][i]-fa[y][i]) {
ans=min(ans,min(w[x][i],w[y][i]));
x=fa[x][i]; y=fa[y][i];
} //一起跳
return min(ans,min(w[x][0],w[y][0])); //最后答案
}
int main() {
n=read(),m=read();
for(int i=1;i<=m;i++) {
int u=read(),v=read(),w=read();
// add(u,v,w); add(v,u,w);
E1[i].x=u; E1[i].y=v; E1[i].dis=w;
} kruskal();
for(int i=1;i<=n;i++)
if(!vis[i]) {
deep[i]=1; dfs(i);
fa[i][0]=i; w[i][0]=INT_MAX;
} q=read();
for(int i=1;i<=20;i++)
for(int j=1;j<=n;j++) {
fa[j][i]=fa[fa[j][i-1]][i-1]; //fa[j][i] 为 j 向上走 2^i 个节点到达的节点
w[j][i]=min(w[j][i-1],w[fa[j][i-1]][i-1]); //w[j][i] 为 j 向上走 2^i 个节点经过路径的权值最小值
} while(q--) printf("%d\n",LCA(read(),read()));
return 0;
}