• P1967 货车运输 题解


    CSDN同步

    原题链接

    简要题意:

    给定一个无向图,若干组询问问 (x ightarrow y) 所有路径上最小权值的最大值。

    算法一

    对于 (60\%) 的数据,(1 le n < 10^3,1 le m < 5 imes 10^4,1 le q< 10^3)

    如果您有网络流板子的话,可以认为,本题是 给定网络图,若干组流量询问,直接 ( ext{ISAP}) 乱跑就可以得到 (O(qnm)) 的优秀时间,轻松得到 (60pts) 及以上。(网络流算法时间跑不满)

    说句闲话:这题网络流怎么优化

    时间复杂度:(O(qnm)). 期望得分:(60pts). 实际得分 100pts

    算法二

    对于 (100\%) 的数据,(1 le n < 10^4 , 1 le m < 5 imes 10^4 , 1 le q< 3 imes 10^4 , 0 le z le 10^5).

    首先,我们考虑的是,如果,(x ightarrow u (w_1))(u ightarrow v (w_2)) ,与 (x ightarrow v (w_3)) 同时存在的话,如果 (min(w_1,w_2) leq w_3),可以直接把 (w_3) 扔掉;走的时候肯定是走 (x ightarrow w ightarrow v) 了。

    那么,我们得到了网络流的贪心算法 显然,我们需要对每一组 (x ightarrow y) 都把这样的 废边 去掉。

    嗯?所以我们要把所有 (x ightarrow y) 的最大路径取出来,对吧?

    那么,最大生成树 就映入我们眼帘:显然这是 最大生成树重构图

    求最大生成树并只留下这个树上的边(重构),那么 (x ightarrow y) 肯定是最大路径(不然肯定换边了)。

    那么如何应对询问?显然,我们在 树上求两点路径( ext{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
    ",LCA(read(),read()));	
    	return 0;
    }
    
    
  • 相关阅读:
    【转】 JavaSE学习笔记(3)---面向对象三大特性
    【转】 JavaSE学习笔记(2)---面向对象基础
    【转】 JavaSE学习笔记(1)---数据类型、运算符、控制结构
    对象的循环
    对象的基本操作
    记录字符串出现的次数
    截取文件的扩展名
    字符串的截取 随机验证码
    字符串常用方法
    选择排序算法
  • 原文地址:https://www.cnblogs.com/bifanwen/p/12814081.html
Copyright © 2020-2023  润新知