http://www.cnblogs.com/LyonLys/archive/2012/05/19/poj_3697_Lyon.html(转载请标明出处)
http://poj.org/problem?id=3697
poj的一道图论题。刚开始用太多STL的容器,所以TLE了2次。
简单描述题目的意思,USTC校园的网络是一个巨大的网络。最初,每两台计算机之间都由一条双向的网线连接着,其中一台是BBS服务器。可是,最近网络中的某些网线被破坏了,网络管理员想知道还有多少台计算机直接或间接的连接着服务器。
一眼看上去,这题可以用并查集来解决,但是数据规模是n=10000个点,m=1000000条边,所以如果用并查集做,时间显然是会非常长的。然后,从边的数目可以发现,这是一幅比较稀疏的无向图,因此,我们可以从边的遍历来考虑。怎么可以用到这些边呢?因为我们要知道,有多少台计算机连接着服务器,因此,我们将可以根据原来与连接的但后来损坏了的网线找出现在与服务器直接连接的计算机有哪些。同样的,对于每一台上一步中被找出的计算机,我们将可以用同样的方法找出下一批间接连接的计算机。
好的,现在看回上面的操作,是不是发现跟bfs十分相似呢?所以我们可以用队列储存每一个需要被检查的元素,用一个布尔数组储存元素是否曾经入队。这个布尔数组还有一个用处,就是可以在最后用于统计有多少台计算机连接着服务器,因为入队的元素都应该连接着服务器的。然而,最大的一个问题是,如果对于每一个队列中的元素都要遍历除它以外的所有计算机来找出有哪些计算机是连接着的,那么时间复杂度就是O(n^2),这样是会超时的。但是,我们可以发现,其实我们可以只是判断那些还没被确定的计算机是否连接着服务器,所以我们需要一个set来储存那些还没确定的计算机编号,然后每次检测的时候就遍历一下这个set。初始状态是,除了服务器外,所有计算机都被加入set中。
于是,加上遍历端点(可能全部计算机都入队了)的时间,总的理论时间复杂度是O(n+m),应该是可以在限定的时间内解决最坏情况的。然而,为了减少代码量,我们需要用到STL的容器。刚开始,我是用set来储存断了的边,但是后来debug时发现,如果用set,在读入比个并储存1000000条边的时候就已经就已经TLE了……囧。可能是因为set用的是RBT,因此每次插入结点的时候时间常数太大了。然后,我就用了一个priority_queue数组来实现同样的功能。
下面是我的代码,仅供参考(注释掉的那几行是debug以及之前用set数组的代码):
1 #include "cstdio" 2 #include "cstdlib" 3 #include "cstring" 4 #include "cmath" 5 #include "cctype" 6 #include "vector" 7 #include "set" 8 #include "map" 9 #include "string" 10 #include "algorithm" 11 #include "stack" 12 #include "queue" 13 14 #define INF 0x7fffffffffffffff 15 #define reset(a) memset(a, 0, sizeof(a)) 16 #define copy(a, b) memcpy(a, b, sizeof(b)) 17 #define FMAX (1E300) 18 #define MAX 1000000000 19 #define feq(a, b) (fabs((a)-(b))<1E-6) 20 #define flq(a, b) ((a)<(b)||feq(a, b)) 21 #define MAXN 10005 22 #define BASE 137 23 #define PASS puts("pass") 24 #define filein freopen("test.in", "r", stdin) 25 #define fileout freopen("test.out", "w", stdout) 26 #define eps 1e-4 27 #define max2(a, b) ((a) > (b) ? (a) : (b)) 28 #define min2(a, b) ((a) < (b) ? (a) : (b)) 29 30 using namespace std; 31 32 //set<int> con[10001]; 33 priority_queue<int, vector<int>, greater<int> > con[10001]; 34 set<int> ns; 35 set<int>::iterator it; 36 bool vis[10001]; 37 queue<int> Q; 38 39 void init(int n){ 40 ns.clear(); 41 while (Q.size()) 42 Q.pop(); 43 Q.push(1); 44 for (int i = 2; i <= n; i++){ 45 while (con[i].size()) 46 con[i].pop(); 47 //con[i].clear(); 48 vis[i] = false; 49 ns.insert(i); 50 } 51 while (con[1].size()) 52 con[1].pop(); 53 //con[1].clear(); 54 vis[1] = true; 55 //PASS; 56 } 57 58 int main(){ 59 //filein; 60 int Case = 0; 61 int n, m; 62 int a, b; 63 64 while (~scanf("%d%d", &n, &m) && (m || n)){ 65 init(n); 66 //PASS; 67 while (m--){ 68 scanf("%d%d", &a, &b); 69 con[a].push(b); 70 con[b].push(a); 71 } 72 //PASS; 73 while (Q.size()){ 74 int cur = Q.front(); 75 76 Q.pop(); 77 //PASS; 78 for (it = ns.begin(); it != ns.end();){ 79 int i = *it; 80 81 while (con[cur].size() && con[cur].top() < i) 82 con[cur].pop(); 83 //if (!con[cur].count(i)){ 84 if (!con[cur].size() || i != con[cur].top()){ 85 if (!vis[i]){ 86 Q.push(i); 87 vis[i] = true; 88 } 89 ns.erase(it++); 90 } 91 else 92 it++; 93 } 94 } 95 96 int cnt = 0; 97 98 for (int i = 2; i <= n; i++) 99 cnt += vis[i]; 100 101 printf("Case %d: %d\n", ++Case, cnt); 102 } 103 104 return 0; 105 }
网上有些题解说是用hash表来做,但是我还没想到怎样做,应该那样子时间会少很多吧。
附带上我的大数据生成代码:
1 #include "cstdio" 2 3 #define fileout freopen("test.in", "w", stdout) 4 5 int main(){ 6 fileout; 7 printf("10000 1000000\n"); 8 9 int cnt = 0; 10 11 for (int i = 10; i < 10000 && cnt < 1000000; i++){ 12 for (int j = 1; j <= 10000 && cnt < 1000000; j++){ 13 printf("%d %d\n", i, j); 14 cnt++; 15 } 16 } 17 18 return 0; 19 }
——written by Lyon
前面的路还长着,还要继续努力!