洛谷题解P1967 货车运输
今天写了一题P1967货车运输
标准的\(lca\)题目
前置知识:
- 链式前向星
用来存图,实现步骤极其简单 - 最大生成树
以前写过最小生成树,运用\(kruskal\)算法可以进行生成
对于读入进来的每一条边,我们做一遍最大生成树
就得到了\(n-1\)条最大生成树中的边
逐次访问这\(n-1\)条边,
需要建一个特殊的树,通过并查集来实现
该树特征:对于一个非叶节点,该节点的权值代表左子树的所有节点到右子树中的所有节点的答案(该树共有\(2n-1\)个节点) - \(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;
}