P1967 货车运输 题解

CSDN同步

原题链接

简要题意:

给定一个无向图,若干组询问问 \(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;
}

posted @ 2020-05-01 17:24  bifanwen  阅读(190)  评论(0编辑  收藏  举报