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,v2 ,.......,vx,那么 v1,v2 ,.......,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 }