• 蓝桥杯--- 历届试题 国王的烦恼 (并查集)


      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。

    题目解析:
    开始的时候还SB的认为是只有一个岛屿全部的桥废掉才会暴乱,然后排序,查找,果断WR(竟然还对了30%,醉了偷笑),其实是在出现新的连通分支的时候就要是暴乱的一天了。。。

    喜欢贴上曾经错误的代码,至少可以留一点屌丝的回忆不是么。。。
    错误代码:
    #include<iostream>
    #include<string>
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    using namespace std;
    int island[10005],leftisland;//剩下的岛屿 
    struct Br{
    	int x1,x2,day;
    }bri[100005];
    bool cmp(Br a,Br b){
    	return a.day<b.day;
    }
    int main(){ 
      int n,m;
      int day = 1,leftbri,count=1,fightday=0;//计天数  剩下的桥梁 桥梁计数  暴乱天数 
      memset(island,0,sizeof(island));
      
      cin>>n>>m;
      leftbri = m;
      for(int i=1;i<=m;i++){
      	cin>>bri[i].x1>>bri[i].x2>>bri[i].day;
      	island[ bri[i].x1 ]++,island[ bri[i].x2 ]++;
      }
      sort(bri+1,bri+m+1,cmp);
       
      while(leftbri>0){
      	int mark=1;
      	if(bri[count].day==day)
      	   while(leftbri>0&&bri[count].day==day){
      	      island[ bri[count].x1 ]--,island[ bri[count].x2 ]--;
      	      if( mark==1&&day!=1&&island[ bri[count].x1 ]==0) fightday++,mark=0;
    		  if( mark==1&&day!=1&&island[ bri[count].x2 ]==0) fightday++,mark=0;
    		  count++;
    	      leftbri--;
    
           }
        day++;
      }
      cout<<fightday<<endl; 
      return 0;
    } 


    题目最终转化为了求连通分支的数目,但是因为随这时间是要不断查找的,如果出现了一次时间就查找一次的话,并查集好像没有这么强大,并且时间也是不允许的,所以我们巧妙的转化一下,转化为按照时间的从后向前的顺序重新建一遍树,这样相当于逆向的把树重新建了一遍,成功的在一次遍历的时候就完成了建树过程,逆向完成。。。
    代码:
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define INF 0x3f3f3f3
    using namespace std;
    int n,m;
    int father[10005];
    struct edge{
        int u,v,c;
    }e[100005];
    int findf(int x){
        return  father[x]<0 ? x : father[x]=findf(father[x]);
    }
    bool cmp(edge a,edge b){
        return a.c>b.c;
    }
    int main(){
        cin>>n>>m;
        for(int i=0;i<m;i++)
    	  cin>>e[i].u>>e[i].v>>e[i].c;//分别输入的是根节点和子节点 
        sort(e,e+m,cmp);//按照维持天数从大到小排序 
        memset(father,-1,sizeof(father));
    //    cout<<endl<<endl; 
    //    for(int i=0;i<m;i++)
    //	  cout<<e[i].u<<' '<<e[i].v<<' '<<e[i].c<<endl;
    	int sum=0,time=-1 ;
        for(int i=0;i<m;i++){
            int xx=findf(e[i].u),yy=findf(e[i].v);
            if(xx!=yy){
            	father[xx]=yy;
    		    if(time!=e[i].c){
                   time=e[i].c;
    		       sum++;
                }
            }
        }
        cout<<sum<<endl;
        return 0;
    }
    一直不太习惯写Union函数,但是好像写了之后变得特别清晰,以后还是要这样多多联系写Union函数
    贴一下网友比较偏亮的代码:
    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    #define maxn  10000+5
    #define maxm  100000+5
    using namespace std;
    int f[maxn],lastday;
    struct node
    {
        int x, y, v;
    } N[maxm];
    
    bool cmp (node a,node b){
    	return a.v>b.v;
    }
    
    int Find(int x){             //找 x 所在的树根
        return f[x]==x?x:f[x]=Find(f[x]);
    }
    
    bool Union(int x, int y){
        int tx=Find(x),ty=Find(y);
        if(tx==ty)  return false;           //它俩在同一个连通分量
        f[tx]=ty;                 //合并两个连通分量
        return true;
    }
    int main(){
        int n, m;
        while(~scanf("%d%d",&n,&m)) {
            for(int i=0;i<m;i++) 
    		  scanf("%d%d%d",&N[i].x,&N[i].y,&N[i].v);
            sort(N,N+m,cmp);
            for(int i=0;i<=n;i++) 
    		   f[i]=i;       //初始化并查集
            int cnt=0;
            lastday=-1;
            for(int i=0;i<m;i++) {
                if(Union(N[i].x,N[i].y) && N[i].v!=lastday){ //两个小岛不连通,且与上一个大桥的天数不同
                    cnt++;
                    lastday=N[i].v;
                }
            }
            printf("%d
    ", cnt);
        }
        return 0;
    }

    下面再做一下小总结:
    关于并查集在树的连通性上发挥着巨大的作用,能够在查的同时实现和并,简化时间复杂度,另外深刻的认识到并查集远不止用来建树的,完全可以向这道题一样把拆树想象成逆向的建树,这样就完成了转化,照样可是实现问题的完美解决。
    另外最近发现以下感觉和以前做过的类型相似的,但是好像又不一样,这样的话不知道怎么入手,那么就可以逆向思维,或许就会柳暗花明了。。。



  • 相关阅读:
    SMC状态机笔记
    Centos重启关机命令
    什么是YAML?
    什么是TOML?
    一键彻底关闭Win10自带Windows Defender杀毒软件
    【接单】找我付费定制Python工具软件或网站开发、Chrome浏览器插件、油猴脚本
    不定积分的符号定义问题
    林群:从数学谈教育
    linux没有ifconfig命令解决办法
    linux使用mitmproxy报错
  • 原文地址:https://www.cnblogs.com/zswbky/p/5431949.html
Copyright © 2020-2023  润新知