初探图论,这本来是大一就该学的,没想到拖到这个假期,感觉好懒啊。
尝试做了一下NOIP 2015 day1 的信息传递,整理如下。
题目
源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #include <iostream> #include <cstdio> using namespace std;
int main() { int n,i,j=0; cin>>n; int circle[n+2]={0},status[n+2]={0},via[n+2]={0},flag[n+2]={0}; for( i = 1 ; i <= n ; i++ ) { cin>>circle[i]; status[circle[i]]++; } for( i = 1 ; i <= n; i++ ) { if(status[i]==0) { flag[j++]=i; via[i]=1; } } for( i = 0 ; i < j ; i++ ) { status[circle[flag[i]]]--; if(status[circle[flag[i]]]==0) { flag[j++]=circle[flag[i]]; via[circle[flag[i]]]=1; } } int ans=n,temp; for( i = 1 ; i <= n ; i++ ) { if( via[i]==0 && circle[i]!=0 ) { via[i]=1; temp=1; j=circle[i]; while(!via[j]) { via[j]=1; j=circle[j]; temp++; } if(temp<ans) ans=temp; } } printf("%d",ans); return 0; }
|
解析
题意说明
代码大量参考了此文章
文中的没有给出详细解题信息,我看了之后整理了一下。
文中有这样一句话
其实这道题题意非常明确,就是让我们寻找一个最小环的长度,怎么求呢?也很简单,先把入度为0的点删除,然后把这个点指向的点的入度-1,如果入度为0,也删去,这样就只保留有用的点,那么从任意一个点开始,用vis数组记录是否被访问过,访问到一个新节点就累加计数器,然后就做出来了.bfs和dfs都可以.
把样例输入数据变成图,就成了这样:
从图中可以看到,第一个人讲自己所知道的信息告诉第二个人,第二个人告诉第四个人,第三个人告诉第二个人,第四个人告诉第三个人,第五个人告诉第一个人。这样,仔细一看,2绝对不可能从1处获得自己的生日(游戏结束条件)
很容易看出,2将自己的生日告诉3,3告诉4,4告诉2,2获得了自己的生日,游戏结束,对于3和4来说也同样是这样。
所以,这个题简短来说,就是找到图中的最小环。
解题思路
首先,由于5—>1—>2并不构成圈,所以2不可能从1处获得自己的信息,所以干脆将5–>1–>2这条路去掉,这样,只需要统计形成圈的有几个人就行了,若有多个圈,找出最小的圈就好。
代码分析
存储数据,计算权值
1 2 3 4 5
| for( i = 1 ; i <= n ; i++ ) { cin>>circle[i]; status[circle[i]]++; }
|
这一段意思很明显,输入数据,然后,记录每个人有几个人告诉他信息
找到权值为0的人
权值为0,也就是示范数据中的5号,没有任何人将自己的信息告诉他
1 2 3 4 5 6 7 8
| for( i = 1 ; i <= n; i++ ) { if(status[i]==0) { flag[j++]=i; via[i]=1; } }
|
flag用于标记权值为0的人的编号
去除权值为0的点
1 2 3 4 5 6 7 8 9
| for( i = 0 ; i < j ; i++ ) { status[circle[flag[i]]]--; if(status[circle[flag[i]]]==0) { flag[j++]=circle[flag[i]]; via[circle[flag[i]]]=1; } }
|
计算圈的成员数目,找到最小的圈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| int ans=n,temp; for( i = 1 ; i <= n ; i++ ) { if( via[i]==0 && circle[i]!=0 ) { via[i]=1; temp=1; j=circle[i]; while(!via[j]) { via[j]=1; j=circle[j]; temp++; } if(temp<ans) ans=temp; } }
|
一点点小总结
可能我的解析说得也并不比原文详细多少,实际上我是跟着代码一步一步调试,才看懂原文的代码的
。
然后,我剔除了原文的队列,使用了一个数组代替,导致我不得不更新数组的有效长度,也就是文中的j值。反而让代码变得更加难以理解。不过,也照顾了一下不知道队列的小伙伴。
然后,原文使用了
1
| memset(to, 0, sizeof(to));
|
来将数组清零,有时候,会忘记这个函数的用法,所以我用了下面的办法
这样,只要初始化数组的第一个元素为0,数组的其他元素会全都变成0。
生命重在折腾