货车运输
Posted guiyan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了货车运输相关的知识,希望对你有一定的参考价值。
思路:
首先是要建最大生成树,不难发现,对于两点$u,v$,如果$u->v$中最小的边的权值最大,那么这条路径$u->v$一定在最大生成树上。
考虑用$Kruskal$建最大生成树,那么首先对边按照从大到小进行排序。
然后用并查集维护两个点的联通性,如果$u$与$v$不在同一个并查集中就合并,注意到合并的同时,我们保证了这样的一个信息:
在对第$i$条边操作的时候,假设合并了并查集$S1$与并查集$S2$,那么显然有:(记第$i$条边的边权为$V_i$)
性质1.集合$S1$内点,集合$S2$内点分别可以相互到达 (由前面$Kruskal$建最大生成树的流程得到的)
性质2.集合$S1,S2$内,任意两点之间的边权的权值都比$Ki$大(因为已经按照边权排序了)
那么我们不难发现,从集合$S1$内的点出发,到集合$S2$内的点,所经过的路径中,边权最小的即为$V_i$,换而言之,我们现在的目标是维护这个东西。也就是说,对于某一次询问$u$到$v$,我能告诉你$u$与$v$两个点被合并到一个集合时,通过的边的边权。(这一段感性理解,因为打字不好描述)
如何维护?/流程,做法
为了方便起见,我们认为$V_i$表示第$i$条边的边权。(貌似这个做法又被叫做$Kruskal$重构树?)
仍然按照$Kruskal$建最大生成树的方法,对边按照边权排序。
//下面是我自己可能也看不懂的解释,但还是强行解释了一波流程...$QAQ$
(您可以忽略这一段,直接跳到下面清晰好看的字体附近)
第一步,首先考虑连接两个点的边,假设边$i$连接了点$u$,$v$,那么我可以建立一个节点,这个点的点权为$V_i$,然后其拥有两个儿子,分别指向$u$和$v$,那么我询问$u$到$v$的路径长度,就是$u$,$v$两点的父亲。然后把$u$,$v$合并到一个并查集中,表示$u$,$v$联通。然后记这个并查集为$S1$,其最顶上的根节点记为$root1$,这样做,我们就建出了一个二叉树。
然后考虑假设边$i$连接了两个大小为$2$的并查集$S1$,$S2$,这个大小为$2$的并查集即第一步建立的并查集,那么我建立一个节点$k$,其点权为$V_i$,那么其两个儿子则分别指向并查集$S1,S2$的$root1,root2.$不难发现,从并查集$S1$中的点到并查集$S2$中的点,必须要经过点$k$。那么集合$S1$中的点到集合$S2$中的点所经过的点,就是在以此法建出来的树中,经过的点的点权最小值,也就是$k$的点权。
依次类推,对于一条边$i$,如果其连接了并查集$S1$和$S2$,仍然有之前得到的性质$1$和性质$2$,所以建立一个节点$k$,令其二个儿子分别指向$S1$和$S2$的$root$,然后令其点权为$V_i$。
简化建树流程如下:
> 1.按照边权排序
> 2.对于一条边所指向的两个点,判断其是否在同一集合,若不在,合并两个集合,把这条边当作一个树点,新建节点,权值为这条边的边权,令其两个儿子指向这两个集合的树的祖先root。
> 3.不难发现,对于u->v的路径,其在最大生成树中唯一,且在重构树中也唯一,即从叶子节点u到叶子节点v的路径。
> 4.同时,由于建树流程只是在Kruskal建树流程加了一步,所以我仍然有前面所提到的性质1和性质2,所以我们有对于一个父亲节点,若两个儿子为边,则儿子权值一定比父亲大(性质2)。所以询问集合S1到集合S2的最小限重,就是这个点的点权。
> 5.对于第4点反过来考虑,询问u->v的答案,实际上就是询问重构树中,点u和点v的LCA的权值
即对于询问$u->v$最小限重,我们求出其$LCA$,然后返回$LCA$的权值即可。
所以我们的问题变成,在重构的树上求$LCA$了。。。
所以用$Trajan$算法呗......
复杂度貌似就排序时会有$log$,总复杂度$O(MlogM)$
代码://笔者写得比较丑......因为弱.......
#include<bits/stdc++.h>
using namespace std;
int read(){
char cc = getchar(); int cn = 0;
while(cc > ‘9‘ || cc < ‘0‘) cc = getchar();
while(cc >= ‘0‘ && cc <= ‘9‘) cn = cn * 10 + cc - ‘0‘, cc = getchar();
return cn;
}
const int M = 50000 * 2 + 5;
struct E{
int to, w, next, from;
bool operator < (const E &a) const{
return w > a.w;
}// 重定义< 号
}e[M * 2];
struct Tree{
int son[2], fa, val, p, root; //p表示这个点为边还是点
}t[M * 2];
int head[M], cnt, book[M], n, m, fa[M], tot, ans[M], top[M];
int Head[M], To[M], Next[M], Id[M], tot1;
//加边
void add(int x, int y, int z)
{
e[++cnt].to = y; e[cnt].from = x; e[cnt].w = z;
e[cnt].next = head[x]; head[x] = cnt;
}
//---并查集
//查找
int find(int x){
if(x == fa[x]) return x;
return fa[x] = find(fa[x]);
}
//合并
void merge(int x, int y){
int u = find(x), v = find(y);
fa[u] = v;
}
//---重构树---
int find_top(int x){ //询问root,即当前点x的祖先
while(t[x].fa != 0) x = t[x].fa;
return x;
}
void merge1(int x, int y, int w){//新建节点,同时将两棵树变为此点儿子
t[++tot].son[0] = x; t[tot].son[1] = y;
t[tot].val = w;
t[x].fa = tot; t[y].fa = tot;
t[x].root = 0, t[y].root = 0, t[tot].root = 1;
}
void Kruskal(){//重构树
for(int i = 1; i <= n; i++){
fa[i] = i;//并查集初始化
t[++tot].p = 1; t[tot].val = i; t[tot].root = 1, top[i] = i;//每个节点对应的树初始化
}
sort(e + 1, e + cnt + 1);//排序
for(int i = 1; i <= cnt; i++){
int u = find(e[i].from), v = find(e[i].to);
if(u != v){//不在同一个并查集
merge(u, v);
int u1 = find_top(top[e[i].from]); //top记录了i号点,在当前情况下,祖先是谁,在询问i号的时候更新其top,防止跳跃次数比较多,算是一个小优化
int v1 = find_top(top[e[i].to]);
merge1(u1, v1, e[i].w);
top[e[i].to] = tot; top[e[i].from] = tot;
}
}
}
//---询问---Trajan
void add_q(int x, int y, int z){
To[++tot1] = y; Id[tot1] = z;
Next[tot1] = Head[x]; Head[x] = tot1;
}
void dfs(int now){
book[now] = 1;
if(t[now].p){
for(int i = Head[t[now].val]; i; i = Next[i])
if(book[To[i]]) ans[Id[i]] = t[find(To[i])].val;
return ;
}
dfs(t[now].son[0]); fa[t[now].son[0]] = now;
dfs(t[now].son[1]); fa[t[now].son[1]] = now;
}
void input(){
n = read(); m = read();
int x, y, z;
for(int i = 1; i <= m; i++)
{
x = read(); y = read(); z = read();
add(x, y, z);
add(y, x, z);
}
}
void solve(){
int p, x, y;
p = read();
for(int i = 1; i <= p; i++)
{
x = read(); y = read();
add_q(x, y, i); add_q(y, x, i);
}
for(int i = 1; i <= tot; i++) fa[i] = i;
t[0].val = -1; //因为可能图并不联通,所以会建出多棵重构树,然后令这些重构树的父亲都为0号点。那么其实通过0号点到达点,本质上是并不连通的,LCA为0,其LCA的权值为-1
for(int i = 1; i <= tot; i++){
if(t[i].root == 1)//这个点为一个根节点
dfs(i);//dfs,trajan
fa[i] = 0; //然后把这个点父亲改为0
}
for(int i = 1; i <= p; i++) printf("%d
", ans[i]);
}
signed main()
{
input();
Kruskal();
solve();
return 0;
}
以上是关于货车运输的主要内容,如果未能解决你的问题,请参考以下文章