JOISC2014 Day2 E "交朋友" (思维+假的SCC)

传送门

 

题目描述
你是活跃在历史幕后的一名特工,为了世界和平而夜以继日地努力着。
这个世界有N个国家,编号为1..N;
你的目的是在这N个国家之间建立尽可能多的友好关系。
你为了制定一个特工工作的计划,作出了一张当今国际关系的示意图。
你准备了一张非常大的画纸,先画下了代表每个国家的N个点。
接下来,为了表示现在的国际关系,画下了M个连接两个国家的有向边;
其中从国家u连向国家v的有向边,表示国家u向国家v派遣了大使,下文称作边(u,v)。
这样就做出了N个点M条边的当今国际关系示意图。

作为两国友好关系的开端,两国之间需要进行「友好条约缔结会议」,以下简称会议。
如果某两个国家p和q要进行会议,那么需要一个向两国都派遣了大使的国家x作为中介。
会议结束后,会议的双方相互向对方的国家派遣大使。
换句话说,为了让国家p和国家q进行会议,必须存在一个国家x满足边(x,p)和边(x,q)都存在;
并且在会议后添加两条边(p,q)和(q,p)(如果需要添加的某条边已经存在则不添加)。
你的工作是对于可以进行会议的两国,选择会议的中介并促使会议进行。
使用这张图进行工作的模拟的话,世界距离和平还有多远的一个重要的基准就是这张图上的边数。

现在给出国家的个数以及当今国际关系的情报,请你求出反复选择两个国家,促使它们进行会议后,图上最多会有多少条边。

输入
第一行两个空格分隔的整数N和M,分别表示世界上国家的个数和图中的边数。
接下来M行描述画纸上的有向边的信息,其中第i行有两个空格分隔的整数ai和bi,表示图中有一条从ai到bi的有向边。

输出
输出一行一个整数,表示能实现的边数的最大值。
注意这个边数包括原有的边数和新连接的边数。
题目描述
样例输入
5 4
1 2
1 3
4 3
4 5

样例输出
10

样例解释
国家1作为中介国,国家2与3开会。
国家4作为中介国,国家3与5开会。
国家3作为中介国,国家2与5开会。
样例输入输出

数据范围:1≤N≤105,1≤M≤2×105,1≤ai,bi≤N,ai≠bi,(ai,bi)≠(aj,bj)

 

参考资料:

  [1]:http://icpc.upc.edu.cn/blog/?p=108

 

题意(摘抄自[1]):

  如果存在有向边<a,b>和<a,c>,就添加两条有向边<b,c>和<c,b>(已经存在就不添加),问最多有多少条边;

题解:

  昨天比赛做这道题的时候,想到了SCC,但我真的用SCC了,中间过程处理的不好(代码写搓了,逃)........

  今天晚上补这道题,看了看题解,和昨天自己想的差不多,就是代码实现上,[1]并没有用SCC知识用SCC思考这道题的做法;

  哎,欠缺的还是太多了,理解了将近半个小时,终于理解了,tql;

  下面谈谈我的进一步理解:

  (看会专业课先,理解明天写,下周要考两门,orz)

  对于某节点 u,如果 u 有 > 1 个儿子 v1,v,.......,vx,那么 v1,v,.......,vx  及其所有儿子可构成强连通;

  例如:

  

  节点①有两个儿子②③,那么②及其所有儿子④⑥,③及其所有儿子⑤构成强连通;

  即②③④⑤⑥构成强连通;

  同属于同一个强连通的所有节点的信息可以集中到一个节点上,假设这5个节点的信息集中到节点②上;

1 int fa[maxn];///fa[i]:i所处的强连通的代表节点
2 ll tot[maxn];///tot[i]:i所处的强连通的个数,只有代表节点的tot[i]才有意义

  fa初始化为-1,tot初始化为1;

  对于上图,合并后的信息为:

  fa[2]=-1,fa[3,4,5,6]=2;

  tot[2]=5;(只有②节点的tot有用)

  那么,最重要的就是合并操作,具体如下:

 1 int Find(int x)///查找x所处的强连通的代表节点
 2 {
 3     return fa[x] == -1 ? x:fa[x]=Find(fa[x]);
 4 }
 5 void DFS(int u,int x)
 6 {
 7     for(int i=head[u];~i;i=G[i].next)
 8     {
 9         int y=G[i].to;
10 
11         y=Find(y);
12         if(x == y)
13             continue;
14 
15         fa[y]=x;
16         tot[x] += tot[y];
17         DFS(y,x);
18     }
19 }
20 
21 ///合并
22 bool update=true;
23 while(update)
24 {
25     update=false;
26     for(int u=1;u <= n;++u)
27     {
28         ///判断u是否已经处于某个强连通中
29         ///如果u处于某个强连通中,x=u,反之x=-1
30         int x=tot[Find(u)] == 1 ? -1:u;
31         for(int i=head[u];~i;i=G[i].next)
32         {
33             /**
34                 如果u不处于某个强连通中:
35                     ①如果其包含>1个儿子节点,其儿子节点可以构成强连通
36                 如果u本身就处于某个强连通中:
37                     ①其所有儿子全部在这个强连通中
38             */
39             int y=G[i].to;
40             if(x == -1)
41                 x=y;
42             else///将强连通中的节点信息合并到Find(x)中
43             {
44                 x=Find(x);
45                 y=Find(y);
46 
47                 if(x == y)///在同一个强连通中
48                     continue;
49 
50                 update=true;
51                 fa[y]=x;
52                 tot[x] += tot[y];
53                 DFS(y,x);///将y的所有儿子节点合并到Find(x)中
54             }
55         }
56     }
57 }

 

AC代码(偷偷换成了我的风格):

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define ll long long
  4 #define memF(a,b,n) for(int i=0;i <= n;a[i]=b,++i);
  5 const int maxn=1e5+50;
  6 
  7 int n,m;
  8 int num;
  9 int head[maxn];
 10 struct Edge
 11 {
 12     int to;
 13     int next;
 14 }G[maxn<<1];
 15 void addEdge(int u,int v)
 16 {
 17     G[num]={v,head[u]};
 18     head[u]=num++;
 19 }
 20 int fa[maxn];///fa[i]:i所处的强连通的代表节点
 21 ll tot[maxn];///tot[i]:i所处的强连通的个数,只有代表节点的tot[i]才有意义
 22 int Find(int x)///查找x所处的强连通的代表节点
 23 {
 24     return fa[x] == -1 ? x:fa[x]=Find(fa[x]);
 25 }
 26 void DFS(int u,int x)
 27 {
 28     for(int i=head[u];~i;i=G[i].next)
 29     {
 30         int y=G[i].to;
 31 
 32         y=Find(y);
 33         if(x == y)
 34             continue;
 35 
 36         fa[y]=x;
 37         tot[x] += tot[y];
 38         DFS(y,x);
 39     }
 40 }
 41 ll Solve()
 42 {
 43     memF(fa,-1,n);
 44     memF(tot,1,n);
 45     bool update=true;
 46     while(update)
 47     {
 48         update=false;
 49         for(int u=1;u <= n;++u)
 50         {
 51             ///判断u是否已经处于某个强连通中
 52             ///如果u处于某个强连通中,x=u,反之x=-1
 53             int x=tot[Find(u)] == 1 ? -1:u;
 54             for(int i=head[u];~i;i=G[i].next)
 55             {
 56                 /**
 57                     如果u不处于某个强连通中:
 58                         ①如果其包含>1个儿子节点,其儿子节点可以构成强连通
 59                     如果u本身就处于某个强连通中:
 60                         ①其所有儿子全部在这个强连通中
 61                 */
 62                 int y=G[i].to;
 63                 if(x == -1)
 64                     x=y;
 65                 else///将强连通中的节点信息合并到Find(x)中
 66                 {
 67                     x=Find(x);
 68                     y=Find(y);
 69 
 70                     if(x == y)///在同一个强连通中
 71                         continue;
 72 
 73                     update=true;
 74                     fa[y]=x;
 75                     tot[x] += tot[y];
 76                     DFS(y,x);///将y的所有儿子节点合并到Find(x)中
 77                 }   
 78             }
 79         }
 80     }
 81 
 82     ll ans=0;
 83     for(int u=1;u <= n;++u)
 84     {
 85         if(fa[u] == -1)
 86             ans += tot[u]*(tot[u]-1);
 87         for(int i=head[u];~i;i=G[i].next)
 88         {
 89             int x=Find(u);
 90             int y=Find(G[i].to);
 91             if(x != y)
 92                 ans++;
 93         }
 94     }
 95 
 96     return ans;
 97 }
 98 void Init()
 99 {
100     num=0;
101     memF(head,-1,n);
102 }
103 int main()
104 {
105     scanf("%d%d",&n,&m);
106     Init();
107     for(int i=1;i <= m;++i)
108     {
109         int u,v;
110         scanf("%d%d",&u,&v);
111         addEdge(u,v);
112     }
113     printf("%lld\n",Solve());
114 
115     return 0;
116 }
View Code

 

posted @ 2019-05-29 21:43  HHHyacinth  阅读(347)  评论(0编辑  收藏  举报