Live2D

洛谷题解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\n",ans[i]);
	return 0;
}
posted @ 2020-09-01 22:51  Wuzhouming  阅读(121)  评论(0编辑  收藏  举报