• 洛谷题解P1967 货车运输


    今天写了一题P1967货车运输
    标准的(lca)题目


    前置知识:

    1. 链式前向星
      用来存图,实现步骤极其简单
    2. 最大生成树
      以前写过最小生成树,运用(kruskal)算法可以进行生成
      对于读入进来的每一条边,我们做一遍最大生成树
      就得到了(n-1)条最大生成树中的边
      逐次访问这(n-1)条边,
      需要建一个特殊的树,通过并查集来实现
      该树特征:对于一个非叶节点,该节点的权值代表左子树的所有节点到右子树中的所有节点的答案(该树共有(2n-1)个节点)
    3. (tarjan)算法(求(LCA)
      对于每一条边,把该边的两个端点所在的集合的顶端间建一个节点,该节点的权值为该边的权值,之后把两个端点和该节点的集合合并起来
      所以我们只要在这颗特定的树中,求两点的(lca)的权值,即答案
      因为我用的是(tarjan),是离线算法,一开始把(ans)数组全部设为-1,后来在求解,
      求不出来的就是-1无解

    在代码中,(tree)只是一个用来存权值的数组
    (e)数组用来存题目所给出的树
    (te)数组存最大生成树
    (q)数组用来存询问时的树
    (ans)存答案,(tot)(tt)是用来存长度用的
    (fa,pre)都是并查集的基础数组

    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    
    using namespace std;
    
    int read() {
    	int w=1,res=0;
    	char ch=getchar();
    	while(ch<'0' || ch>'9') {
    		if(ch=='-') w=-1;
    		ch=getchar();
    	}
    	while(ch>='0' && ch<='9') res=res*10+ch-'0',ch=getchar();
    	return w*res;
    }
    //快读
    
    int n,m,qs;
    
    struct Tree {
    	int l,r,d;
    } tree[200000];
    
    bool vis[200005];
    int ans[300005];
    
    int tot=0,tt=0,head[50005];
    struct node {
    	int to,w,nxt;
    } e[500000],q[600000],te[500000];
    void add(int a,int b,int c) {
    	q[++tot].nxt=head[a];
    	q[tot].to=b;
    	q[tot].w=c;
    	head[a]=tot;
    }
    //链式前向星
    
    int pre[20005],fa[20005];
    int find(int x) {
    	return pre[x]==x ? x : pre[x]=find(pre[x]);
    }
    void bing(int a,int b) {
    	int f1=find(a),f2=find(b);
    	if (f1==f2)return;
    	pre[f2]=f1;
    }
    int findd(int x) {
    	return fa[x]==x ? x : fa[x]=findd(fa[x]);
    }
    void bingg(int a,int b) {
    	int f1=findd(a),f2=findd(b);
    	if (f1==f2)return;
    	fa[f2]=f1;
    }
    //并查集
    
    bool cmp(node e1,node e2) {
    	return e1.w > e2.w;
    }
    
    void clear() {
    	for(int i=1; i<2*n; i++) pre[i]=i;
    }//清空pre数组
    
    void kruskal() {
    	for(int i=1; i<=m; i++) {
    		int u = e[i].to,v = e[i].nxt;
    		if(find(u) != find(v)) {
    			bing(u,v);
    			te[++tt].nxt = e[i].nxt,
    			te[tt].to = e[i].to,
    			te[tt].w = e[i].w;
    		}//将边存入最小生成树
    	}
    }
    //kruskal算法
    
    void built() {
    	for (int i=1; i<=tt; i++) {
    		int u=find(te[i].to),v=find(te[i].nxt);
    		tree[n+i].l=u;
    		tree[n+i].r=v;
    		tree[n+i].d=te[i].w;
    		bing(n+i,u);
    		bing(n+i,v);
    	}
    }
    
    void tarjan(int x) { //一次tarjan只处理一棵森林中的所有询问
    	vis[x] = 1;
    	for(int i=head[x]; i; i=q[i].nxt) {
    		int v = q[i].to;
    		if(vis[v] && find(v) == find(x))
    			ans[q[i].w]=tree[findd(v)].d;
    	}
    	int ld=tree[x].l,rd=tree[x].r;
    	if(ld) tarjan(ld), bingg(x,ld);
    	if(rd) tarjan(rd), bingg(x,rd);
    }
    //tarjan算法
    
    int main() {
    	n=read(),m=read();
    	for(int i=1; i<=m; i++)
    		e[i].to = read(),e[i].nxt = read(),e[i].w = read();
    	qs=read();
    	for(int i=1; i<=qs; i++) {
    		int x=read(),y=read();
    		add(x,y,i),add(y,x,i);
    	}
    	sort(e+1, e+m+1, cmp);
    	clear(),kruskal();
    	clear(),built();
    	memset(ans, -1, sizeof ans);
    	for(int i=1; i<2*n; i++) fa[i] = i;
    //    fa用来存每一次dfs时并查集操作,
    //    pre现在用来存重构树中的森林
    	for(int i=n+1; i<=n+tt; i++)
    		if(pre[i] == i) tarjan(i);
    //    如果找到了树根,那么就dfs
    	for(int i=1; i<=qs; i++) printf("%d
    ",ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    QQ机器人
    Javascript实现base64的加密解密
    C# 对List<T>取交集、连集及差集
    简单正则验证
    中止线程
    线程同步synchronized
    volatile
    并发
    垃圾回收机制
    给定一个正整数num ,反复将各个位上的数字相加,直到结果为一位数
  • 原文地址:https://www.cnblogs.com/Wuzhuoming-sirenboke/p/13599307.html
Copyright © 2020-2023  润新知