zoukankan      html  css  js  c++  java
  • tarjan算法+缩点--cojs 908. 校园网

    ★★   输入文件:schlnet.in   输出文件:schlnet.out   简单对比
    时间限制:1 s   内存限制:128 MB
    USACO/schlnet(译 by Felicia Crazy)

    描述

    一些学校连入一个电脑网络。那些学校已订立了协议:每个学校都会给其它的一些学校分发软件(称作“接受学校”)。注意如果 B 在 A 学校的分发列表中,那么 A 不必也在 B 学校的列表中。

    你要写一个程序计算,根据协议,为了让网络中所有的学校都用上新软件,必须接受新软件副本的最少学校数目(子任务 A)。更进一步,我们想要确定通过给任意一个学校发送新软件,这个软件就会分发到网络中的所有学校。为了完成这个任务,我们可能必须扩展接收学校列表,使其加入新成员。计算最少需要增加几个扩展,使得不论我们给哪个学校发送新软件,它都会到达其余所有的学校(子任务 B)。一个扩展就是在一个学校的接收学校列表中引入一个新成员。

    PROGRAM NAME: schlnet

    INPUT FORMAT (file schlnet.in)

    输入文件的第一行包括一个整数 N:网络中的学校数目(2 <= N <= 100)。学校用前 N 个正整数标识。接下来 N 行中每行都表示一个接收学校列表(分发列表)。第 i+1 行包括学校 i 的接收学校的标识符。每个列表用 0 结束。空列表只用一个 0 表示。

    OUTPUT FORMAT(file schlnet.out)

    你的程序应该在输出文件中输出两行。第一行应该包括一个正整数:子任务 A 的解。第二行应该包括子任务 B 的解。

    SAMPLE INPUT (file schlnet.in)


    2 4 3 0
    4 5 0
    0

    1 0

    SAMPLE OUTPUT (file schlnet.out)

    1
    2
    注意:凡是涉及tarjan算法缩点的,图中缩点后只有一个点的情况必须特判。
     非完美代码(加了特判之后,只过了七个点):
      1 /*一开始没注意到整张图都是一个强连通分量的情况,A任务正确,但是B任务应该输出0,而如果不特判的话,就会输出1(因为是图中的强连通分量数目(1)-可用边(0))*/
      2 #define N 120
      3 #include<iostream>
      4 using namespace std;
      5 #include<cstdio>
      6 #include<cstring>
      7 #include<stack>
      8 stack<int>sta;
      9 bool visited[N]={0},instack[N]={0};
     10 int dfn[N],low[N],topt=0,ut=0;
     11 int father[N];
     12 struct Edge{
     13     int u,v,last;
     14 }edge[N*N*2],usedge[N*N*2];
     15 int uset=0;
     16 int head[N],ushead[N],n,t=0,sumfl=0,sumfa=0;
     17 void add_edge(int u,int v)
     18 {
     19     ++t;
     20     edge[t].u=u;
     21     edge[t].v=v;
     22     edge[t].last=head[u];
     23     head[u]=t;
     24 }
     25 void input()
     26 {
     27     scanf("%d",&n);
     28     for(int i=1;i<=n;++i)
     29     {
     30         int x;
     31         while(true)
     32         {
     33             scanf("%d",&x);
     34             if(x==0) break;
     35             add_edge(i,x);
     36         }
     37     }
     38     for(int i=1;i<=n;++i)
     39       father[i]=i;
     40 }
     41 void tarjan(int k)
     42 {
     43     visited[k]=true;
     44     dfn[k]=low[k]=++topt;
     45     sta.push(k);
     46     instack[k]=true;
     47     for(int l=head[k];l;l=edge[l].last)
     48     {
     49         if(!visited[edge[l].v])
     50         {
     51             tarjan(edge[l].v);
     52             low[k]=min(low[k],low[edge[l].v]);
     53         }
     54         else if(instack[edge[l].v])
     55              low[k]=min(low[k],dfn[edge[l].v]);
     56     }
     57     if(dfn[k]==low[k])
     58     {
     59         sumfl++;
     60         int pp=sta.top();
     61         sta.pop();
     62         instack[pp]=false;
     63         while(1)
     64         {
     65             father[pp]=k;
     66             if(pp==k) break;
     67             pp=sta.top();
     68             sta.pop();
     69             instack[pp]=false;
     70         }
     71     }
     72 }
     73 bool flagout[N]={0},flagin[N]={0};
     74 int find(int x)
     75 {
     76     return (father[x]==x)?father[x]:father[x]=find(father[x]);
     77 }
     78 void count_fa_edge()
     79 {
     80     for(int i=1;i<=t;++i)
     81     {
     82         int u=edge[i].u,v=edge[i].v;
     83         if(!flagin[v]&&!flagout[u]&&father[u]!=father[v])
     84         {
     85             ++ut;/*统计可用边*/
     86             flagin[v]=true;
     87             flagout[u]=true;
     88             
     89         }
     90     }
     91     for(int i=1;i<=t;++i)
     92     {
     93         int u=edge[i].u,v=edge[i].v;
     94         if(father[u]!=father[v])
     95         {
     96             ++uset;/*利用并查集缩点*/
     97             usedge[uset].u=father[u];
     98             usedge[uset].v=father[v];
     99             usedge[uset].last=ushead[father[u]];
    100             ushead[father[u]]=uset;
    101         }
    102     }
    103     for(int i=1;i<=uset;++i)
    104     {
    105         int u=usedge[i].u,v=usedge[i].v;
    106         int r1=find(u),r2=find(v);
    107         if(r1!=r2)
    108         {
    109             father[r2]=r1;/*重新构图*/
    110         }
    111     }
    112     for(int i=1;i<=n;++i)
    113     {
    114          find(i);
    115          if(father[i]==i)
    116          sumfa++;
    117     }
    118 }
    119 int main()
    120 {
    121     freopen("schlnet.in","r",stdin);
    122     freopen("schlnet.out","w",stdout);
    123     input();
    124     for(int i=1;i<=n;++i)
    125       if(!visited[i])
    126          tarjan(i);
    127     count_fa_edge();
    128     if(sumfl==1)
    129     {
    130         printf("1
    0");
    131     }
    132     else 
    133     printf("%d
    %d",sumfa,sumfl-ut);
    134     fclose(stdin);fclose(stdout);
    135     return 0;
    136 }

      上述算法的反例:

        这个算法是算出有多少个强连通分量,和可用边(可用边是满足每个点的入度出度都是1),然后前者减去后者。

        反例:1--》2

                |    /               

                /     |  

                 3----

     这样一张图再加入1条(2--》1)边就可以满足条件了,但是如果上述算法先找到了1--》2这条边,那么1--》3和3-->2都不会找到了,那么就会判断再加入两条边,这个反例就在于 

    加边时的随机性,会对结果有影响。

    完美代码与正确思路:

      1 /*tarjan少写了instack[pp]=false;竟然让我调了半个多小时*/
      2 #define N 110
      3 using namespace std;
      4 #include<cstdio>
      5 #include<stack>
      6 #include<iostream>
      7 #include<cstring>
      8 #include<algorithm>
      9 int n,dfn[N],low[N],topt=0;
     10 struct Edge{
     11     int u,v,last;
     12 }edge[N*N*2];
     13 stack<int>sta;
     14 int indu[N]={0},outdu[N]={0},ust=0,ptot[N];/*indu统计所有点的入度,outdu统计所有点的出度,ptot记录了缩点后图上剩余的点*/
     15 int father[N]={0},t=0,head[N];
     16 bool visited[N]={false},instack[N]={0};
     17 void add_edge(int u,int v)
     18 {
     19     ++t;
     20     edge[t].u=u;/*建图*/
     21     edge[t].v=v;
     22     edge[t].last=head[u];
     23     head[u]=t;
     24 }
     25 void input()
     26 {
     27     scanf("%d",&n);
     28     for(int i=1;i<=n;++i)
     29     {
     30         int x;
     31         while(1)
     32         {
     33           scanf("%d",&x);
     34                   if(x==0) break;
     35           add_edge(i,x);
     36         }
     37     }
     38     for(int i=1;i<=n;++i)
     39       father[i]=i;
     40 }
     41 void tarjan(int k)
     42 {
     43     visited[k]=true;
     44     dfn[k]=low[k]=++topt;
     45     sta.push(k);
     46     instack[k]=true;
     47     for(int l=head[k];l;l=edge[l].last)
     48     {
     49         if(!visited[edge[l].v])
     50         {
     51             tarjan(edge[l].v);
     52             low[k]=min(low[k],low[edge[l].v]);
     53         }
     54         else if(instack[edge[l].v])
     55                 low[k]=min(low[k],dfn[edge[l].v]);
     56     }
     57     if(dfn[k]==low[k])
     58     {
     59         int pp;
     60         ++ptot[0];/*加入强连通分量的代表点*/
     61         ptot[ptot[0]]=k;
     62         pp=sta.top();
     63         sta.pop();
     64         while(1)
     65         {
     66             father[pp]=k;/*缩点*/
     67             instack[pp]=false;/*别忘了把设计为已出栈*/
     68             if(pp==k) break;
     69             pp=sta.top();
     70             sta.pop();
     71         }
     72     }
     73 }
     74 void count_()
     75 {
     76     for(int i=1;i<=t;++i)/*遍历所有的边,把图中剩下的边给相应的剩下的点统计入度出度*/
     77     {
     78         int u=edge[i].u,v=edge[i].v;
     79         if(father[u]!=father[v])
     80         {
     81             outdu[father[u]]++;
     82             indu[father[v]]++;
     83         }
     84     }
     85     int in=0,out=0;
     86     for(int i=1;i<=ptot[0];++i)
     87     {/*遍历图中剩余的点,统计indu==0和outdu==0的点的个数*/
     88         if(!indu[ptot[i]])
     89           in++;
     90         if(!outdu[ptot[i]])
     91           out++;
     92     }
     93     if(ptot[0]==1)/*图中只有一个强连通分量的情况必须特判*/
     94     {
     95         printf("1
    0");
     96     }
     97     else printf("%d
    %d",in,max(in,out));
     98 }
     99 int main()
    100 {
    101         freopen("schlnet.in","r",stdin);
    102     freopen("schlnet.out","w",stdout);
    103     input();
    104     for(int i=1;i<=n;++i)
    105       if(!visited[i])
    106         tarjan(i);
    107     count_();
    108     fclose(stdin);fclose(stdout);
    109     return 0;
    110 } 
  • 相关阅读:
    写一个日志类用于跟踪调试
    Delphi用QJSON解析JSON格式的数据
    Http协议访问DataSnap Rest 服务器
    由于@@ServerName等问题对SQL增量升级脚本进行补充
    自动适应屏幕分辨率
    tnsping命令解析
    delphi 提取字符中的数字
    UltraISO PE(软碟通) v9.6.2.3059 注册码
    cs编写php字符显示问题
    phpMyAdmin安装设置
  • 原文地址:https://www.cnblogs.com/c1299401227/p/5492949.html
Copyright © 2011-2022 走看看