BZOJ 4668 冷战——并查集+LCA

基情链接♂BZOJ 1977 严格次小生成树——浅析最近公共祖先(LCA)

目录

一.题目:冷战

1.题目描述

2.输入

3.输出

4.样例输入

5.样例输出

二.题解

三.代码

谢谢!


一.题目:冷战

1.题目描述

1946 年 3 月 5 日,英国前首相温斯顿·丘吉尔在美国富尔顿发表“铁幕演说”,正式拉开了冷战序幕。美国和苏联同为世界上的“超级大国”,为了争夺世界霸权,两国及其盟国展开了数十年的斗争。在这段时期,虽然分歧和冲突严重,但双方都尽力避免世界范围的大规模战争(第三次世界大战)爆发,其对抗通常通过局部代理战争、科技和军备竞赛、太空竞争、外交竞争等“冷”方式进行,即“相互遏制,不动武力”,因此称之为“冷战”。Reddington 是美国的海军上将。由于战争局势十分紧张,因此他需要时刻关注着苏联的各个活动,避免使自己的国家陷入困境。苏联在全球拥有 N 个军工厂,但由于规划不当,一开始这些军工厂之间是不存在铁路的,为了使武器制造更快,苏联决定修建若干条道路使得某些军工厂联通。Reddington 得到了苏联的修建日程表,并且他需要时刻关注着某两个军工厂是否联通,以及最早在修建哪条道路时会联通。

具体而言,现在总共有M 个操作,操作分为两类:

• 0 u v,这次操作苏联会修建一条连接 u 号军工厂及 v 号军工厂的铁路,注意铁路都是双向的;

• 1 u v, Reddington 需要知道 u 号军工厂及 v 号军工厂最早在加入第几条条铁路后会联通,假如到这次操作都没有联通,则输出 0;

作为美国最强科学家, Reddington 需要你帮忙设计一个程序,能满足他的要求。

2.输入

第一行两个整数 N, M。

接下来 M 行,每行为 0 u v 或 1 u v 的形式。

数据是经过加密的,对于每次加边或询问,真正的 u, v 都等于读入的u, v 异或上上一次询问的答案。一开始这个值为 0。

1 ≤ N, M ≤ 500000,解密后的 u, v 满足1 ≤ u, v ≤ N, u不等于v

3.输出

对于每次 1 操作,输出 u, v 最早在加入哪条边后会联通,若到这个操作时还没联通,则输出 0。

4.样例输入

5 9

0 1 4

1 2 5

0 2 4

0 3 4

1 3 1

0 7 0

0 6 1

0 1 6

1 2 6

5.样例输出

0

3

5

二.题解

这道题第一眼看是并查集,对吧。但是不能直接用并查集做,因为要超时。我们可以将并查集和LCA联用起来。

首先因为我们只要两点相连通就行了。为避免成环,运用并查集判断连点是否连通。

又因为要找两个点最早多久连通,所以将i,即输入的序号当做权值,用一个数组val[i]存权值,表示点i到他父亲的权值,附权值就在启发式合并的时候进行就行了。注意:这里的并查集不能用路径压缩,因为我们后边要用fa[i]来爬山,所以要用启发式合并

不用路径压缩找根结点:

int findSet (int x){
    while (x != fa[x])
        x = fa[x];
    return x;
}

启发式合并+附边权:

void unionSet (int x, int y, int w){
    int u = findSet (x), v = findSet (y);
    if (u == v)
        return ;
    if (Rank[u] >= Rank[v]){
        fa[v] = u;
        val[v] = w;
        if (Rank[u] == Rank[v])
            Rank[u] ++;
    }
    else{
        fa[u] = v;
        val[u] = w;
    }
}

 然后在遇到要输出的时候,先用并查集判断两点是否在同一颗树上,如果在,就进入LCA函数;否则,输出0。首先将每个点的深度预处理一下,然后可以直接用暴力爬山法,先将两点爬到同一深度,再爬到LCA。爬的路程中爬一步比较一下最大权值,最后输出最大权值就行了。

深度预处理:

void prepare (int x){//dep[i]指深度
    if (x == fa[x])
        return ;
    prepare (fa[x]);
    dep[x] = dep[fa[x]] + 1;
}

是不是很简单?注意要异或

三.代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <vector>
using namespace std;
#define M 500005
int fa[M], n, m, Xor, dep[M], Rank[M], val[M], num;
void makeSet (int x){
    for (int i = 0; i <= x; i ++)
        fa[i] = i;
}
int findSet (int x){
    while (x != fa[x])
        x = fa[x];
    return x;
}
void prepare (int x){
    if (x == fa[x])
        return ;
    prepare (fa[x]);
    dep[x] = dep[fa[x]] + 1;
}
void unionSet (int x, int y, int w){
    int u = findSet (x), v = findSet (y);
    if (u == v)
        return ;
    if (Rank[u] >= Rank[v]){
        fa[v] = u;
        val[v] = w;
        if (Rank[u] == Rank[v])
            Rank[u] ++;
    }
    else{
        fa[u] = v;
        val[u] = w;
    }
}
int answer_question (int x, int y){
    int Max = 0;
    prepare (x);
    prepare (y);
    if (dep[x] < dep[y])
        swap (x, y);
    while (dep[x] > dep[y]){
        Max = max (val[x], Max);
        x = fa[x];
    }
    while (x != y){
        Max = max (val[x], Max), Max = max (val[y], Max);
        x = fa[x], y = fa[y];
    }
    return Max;
}
int main (){
    scanf ("%d %d", &n, &m);
    makeSet (n);
    int u, v, flag;
    for (int i = 1; i <= m; i ++){
        scanf ("%d %d %d", &flag, &u, &v);
        u = u ^ Xor;
        v = v ^ Xor;
        if (! flag){
            num ++;
            unionSet (u, v, num);
        }
        else{
            int x = findSet (u), y = findSet (v);
            if (x != y)
                Xor = 0;
            else
                Xor = answer_question (u, v);
            printf ("%d\n", Xor);
        }
    }
    return 0;
}

谢谢!

发布了61 篇原创文章 · 获赞 32 · 访问量 8358

猜你喜欢

转载自blog.csdn.net/weixin_43908980/article/details/90669077