这道题是ACM合肥赛的最后一题,题目大概题意如下:
给定一个包含N(1 ≤ N ≤ 10,000)个顶点的无向完全图,图中的顶点从1到N依次标号。从这个图中去掉M(0 ≤ M ≤ 1,000,000)条边,求最后与顶点1联通的顶点的数目。(时限4s)
基本算法肯定是BFS或者DFS。BFS思想为从顶点1开始不断扩展,广度优先搜索所有的与当前扩展点联通的顶点。开始每次都要判断所有的顶点,因此BFS是O(N^2)的复杂度。
每一次扩展都需要判断该边是否在断开的边的集合中。判断需要的时间取决于M条断开边集的存储方式。初始我是用一个链表存储,索引为该边依附的顶点,然后直接搜索,复杂度为O(N^2×N)=O(N^3),超时。然后我又将每个顶点对应边集进行排序,并且二分搜索,这样也很慢,复杂度为O(N^2×logN)。另外一种思路是改变断开边集的存储方式,直接用10000×10000的bool型数组存储顶点i是否和j联通,这样虽然时间上达到了O(N^2)的复杂度,但是把内存爆掉了。
最终Sempr同学提供了他的解决方法:hash。其实我刚开始也想到了hash,但是想不出如何进行hash存储,看来还是hash用的太少。具体的方法是,通过数组模拟链表,由另外一个数组记录当前hash code对应的节点在数组中的位置。这样的hash让复杂度基本降低到了O(N^2)。
在进行BFS的时候为了防止对那些已经扩展的节点重复扩展,通过一个数组保存那些尚未扩展到节点,只对这些点进行扩展。
最后是一些提示:
1、 在hash 的时候,选择素数进行相关操作会比和数快很多;
2、 Vector的memory pool的方式,使得它的内存占用比实际申请的多的多,因此最好用数组
3、 少用pair,可以用struct pair{int first,second;}来进行模拟。
以下是代码:
- struct node
- {
- int first,second;
- };
- const int mod = 1000117;
- const int smod = 10091;
- node edges[mod];
- int next[mod],hash[mod],endList,q[10010],lst[10010],m,n;
- void insertNode(int a,int b) // a<b
- {
- int code = (a*smod+b)%mod;
- edges[endList].first=a;
- edges[endList].second=b;
- next[endList]=hash[code];
- hash[code]=endList++;
- }
- bool search(int a,int b) // a<b
- {
- if(a>b) swap(a,b);
- int code = (a*smod+b)%mod;
- for (int i=hash[code];i>-1;i=next[i])
- if(edges[i].first==a && edges[i].second==b) return true;
- return false;
- }
- int main()
- {
- int cnt=0,a,b,head,tail,lstSize,cur,temp;
- while (scanf("%d%d",&m,&n))
- {
- if(!m && !n) break;
- endList=0; ++cnt;
- memset(hash,-1,sizeof hash);
- while (n--)
- {
- scanf("%d%d",&a,&b);
- if(a<b) insertNode(a,b);
- else insertNode(b,a);
- }
- lstSize=0;
- for (int i=2;i<=m;++i) lst[lstSize++]=i;
- q[0]=1; head=0; tail=1;
- while (head<tail)
- {
- cur = q[head++];
- temp = 0;
- for (int i=0;i<lstSize;++i)
- {
- if (!search(cur,lst[i])) q[tail++]=lst[i];
- else lst[temp++]=lst[i];
- }
- lstSize=temp;
- }
- printf("Case %d: %d/n",cnt,tail-1);
- }
- return 0;
- }