码迷,mamicode.com
首页 > 其他好文 > 详细

货车运输

时间:2019-01-28 23:09:27      阅读:217      评论:0      收藏:0      [点我收藏+]

标签:using   初始化   方便   put   必须   并查集   include   tor   大小   

思路:

首先是要建最大生成树,不难发现,对于两点$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\n", ans[i]);
}
signed main()
{
    input();
    Kruskal();
    solve(); 
    return 0;
} 

货车运输

标签:using   初始化   方便   put   必须   并查集   include   tor   大小   

原文地址:https://www.cnblogs.com/guiyan/p/10332010.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!