2022.02.20 一道题的心得---国王的烦恼(并查集+逆向思维)

0.引言

这篇文章让我醍醐灌顶,特此记录

1.原题呈现

C国由n个小岛组成,为了方便小岛之间联络,C国在小岛间建立了m座大桥,每座大桥连接两座小岛。两个小岛间可能存在多座桥连接。然而,由于海水冲刷,有一些大桥面临着不能使用的危险。

如果两个小岛间的所有大桥都不能使用,则这两座小岛就不能直接到达了。然而,只要这两座小岛的居民能通过其他的桥或者其他的小岛互相到达,他们就会安然无事。但是,如果前一天两个小岛之间还有方法可以到达,后一天却不能到达了,居民们就会一起抗议。

现在C国的国王已经知道了每座桥能使用的天数,超过这个天数就不能使用了。现在他想知道居民们会有多少天进行抗议。

输入格式
输入的第一行包含两个整数n, m,分别表示小岛的个数和桥的数量。
接下来m行,每行三个整数a, b, t,分别表示该座桥连接a号和b号两个小岛,能使用t天。小岛的编号从1开始递增。

输出格式
输出一个整数,表示居民们会抗议的天数。

样例输入

4 4
1 2 2
1 3 2
2 3 1
3 4 3

样例输出

2

样例说明
第一天后2和3之间的桥不能使用,不影响。
第二天后1和2之间,以及1和3之间的桥不能使用,居民们会抗议。
第三天后3和4之间的桥不能使用,居民们会抗议。

数据规模和约定
对于30%的数据,1<=n<=20,1<=m<=100;
对于50%的数据,1<=n<=500,1<=m<=10000;
对于100%的数据,1<=n<=10000,1<=m<=100000,1<=a, b<=n, 1<=t<=100000。

2.审题

题目看着貌似很简单,其实大有玄机,很多人灾祸于此,其实我们忽略了很多东西。

image.png

请注意,本题是要我们输出反抗的天数,一天内可能有一组边使得其不连通,也有可能有两组使其不连通,但程序是顺序执行的,我们需要加上一个限制条件。

判断桥断开以后是否分开两片的方法很简单,有了合根植物的题目的基础上,我们自然想到并查集进行维护,即看他们的生成元是否相同来判断是否联通。

3.反思

ps:看题解的请忽略~

首先复盘我最初大概错误的思路,当时看了题解还奇怪,为什么都选择逆向做呢?答案是时间复杂度!

还原一下思路。

​ 这可以还原一个无向图。即输入的是一个顶点到另外一个顶点的边,第三个可以看成是权值,其代表的意义是即将毁灭的天数。

很自然的想法就是输入边,权值以后构成一个无向图。

​ 然后题目说的是抗议,那么抗议无非就是从连通到不联通的转变嘛。那我们引入并查集来维护无向图的联通状态,

  1. 按照一开始输入的边,先升序预处理以后,通过并查集,即,将其unite,将所有的边unite合并成n-1条边,即kruskal寻找。
  2. 然后如果能合成n-1条边,就让每个权值-1,然后重复1,直到不连通为止,重复1的次数就是抗议的天数。

首先,我理解错了题意,题目要求我输出抗议的天数,是抗议的总天数,而不是抗议的第一天。

其次,没有其次了,荒谬至极!

正确的逻辑应该是一座桥的毁灭以后,如果存在两个顶点之前相通,限制却不能互通的,那么结果ans+1,请注意,题目是要求前面联通以后后面却不能联通的情况,但如果实现了也不一定能通过全部测试点。

解决两个问题:

桥的毁灭后该怎么重新表示无向图点到点的联通关系??那我们得需要将边重新unite把?因为要遍历整个边,时间复杂度有O(n)

我们输出答案需要判断无向图有没有从联通到不连通的情况把??因为此行为需要遍历整个并查集,判断find(x)==x?这也又是一个O(n)

一座桥毁灭以后我们就需要2*O(n)的时间了。

那按照题目的要求,所有的桥迟早都要毁灭,那外边又得套一个for循环,就是n*(2n),最后的时间复杂度是O(n^2),题目n有10e5,开个方,时间会超出,所以就如大佬所说,这题需要逆向做才会有新世界。

4.题解

其实我们模拟其过程以后,会发现一个问题,经过每一天的流逝,直到所有的桥断裂以后,居民的反抗次数是已经固定的。那我们如果从没有桥开始,然后每当居民反抗以前,桥是好的,也就是状态从不连通到联通了会不会好很多呢??答案是会的!!如果时光流逝的话,我们就需要按损坏时间从大到小逆向排序来建桥。

我们的人生宗旨是不忘初心,这道题也一样,我们此题最主要关注的是,两岛是否联通,所以我们才在图里寻找一个最小生成树,之所以用树状结构是因为树的特点是点到点是可达的且无回路。

所以我们每次判断有一个“帮主”不是自己人的时候,把他拉进同盟(unite),就相当于建桥使其联通,在时间线上就是居民反抗前的时刻。直到此图有顶点数-1条边,此时不管如何,不存在不连通的情况了,至此居民就无反抗的情况。

这里值得注意的是,之前提到过此题的单位是天,也就是每次状态的改变是所有的权值在变化,就是有可能有两个岛或多个岛反抗的情况。所以要加一个限制条件。解决办法也让我醍醐灌顶。就是预知下一个即将建成的桥的天数是否和我这个一样,一样的话直接忽略掉,不算,但是这桥建还是要建的哈。该联通还是要联通。

参考代码,有一个点错了,但是找不到,先放着以便以后学习。。

#include<iostream>
#include<algorithm>
using namespace std;

int n,m;
struct edge
{
    int u;
    int v;
    int w;
};

edge E[(int)10e5+5];

bool cmp(edge a,edge b)
{
    return a.w>b.w;
}

int pre[(int)10e6+5];
int depth[(int)10e6+5];

//bingchaji
void init()
{
    for(int i=1;i<=m;i++)
    {
        pre[i]=i;
        depth[i]=0;
    }
}

int find(int i)
{
    return pre[i]==i?i:pre[i]=find(pre[i]);
}

int kruskal()
{
    int ans=0,last_day=0;
    int num_edge;
    sort(E+1,E+m+1,cmp);
    init();
    for(int i=1;i<=m;i++)
    {
        int find_u=find(E[i].u);
        int find_v=find(E[i].v);
        if(find_u!=find_v)
        //我们只需要将两个不可到达的顶点使其联通,这是居民就会防抗的先前情况
        {
            if(depth[find_u]>depth[find_v])
            {
                pre[find_v]=find_u;
            }
            else{
                if(depth[find_u]==depth[find_v])
                {
                    depth[E[i].v]++;
                }
                pre[find_u]=find_v;
            }
            //避免同一天多个居民反抗结果多加的情况
            if(E[i].w!=last_day)
            {
                last_day=E[i].w;
                ans++;
            }
            num_edge++;
            if(num_edge==n-1)
                break;
        }
    }
    return ans;
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>E[i].u>>E[i].v>>E[i].w;
    }
    cout<<kruskal()<<endl;
    return 0;
}

这位大佬教会了我在具体情景中如何分析时间复杂度,以及审题的情况,还有考虑的诸多因素,值得再回头看一看。

end...

posted @ 2022-02-20 23:40  yuezi2048  阅读(49)  评论(1编辑  收藏  举报