感想详见http://blog.csdn.net/zxy_snow/archive/2011/01/17/6145924.aspx
kosaraju算法解释详见算导338页,把证明认真看完。
我只提示下一些情况。画图吧。框框里的数字是这种情况的答案。注意最下面的两种情况。其他的注释已经写得很详细了。
#include <stdio.h> #include <stdlib.h> #include <iostream> #include <string.h> #include <stack> #define MAX 10005 using namespace std; stack<int> fin; typedef struct vote{ int num; vote *next; }vote; vote *v[MAX],*ve[MAX],node[MAX*6]; int n,m,cou,ind; int sum,sou; int visited[MAX]; int setn[MAX],pre[MAX];//pre存的是,一个强连通分量都由一个点表示 。 int pass[MAX],tmp[MAX];//pass存的是在第二次DFS中本身已经被访问过了 然后又被访问了。 //tmp存的是在第二次DFS中强连通分量的点。 void init() { memset(node,'/0',sizeof(node)); //开的新点从这里面拿,没用malloc memset(v,'/0',sizeof(v)); // 最初的邻接表存这里面 memset(ve,'/0',sizeof(ve)); // v转置后是ve memset(visited,0,sizeof(visited)); // DFS的标记 memset(setn,0,sizeof(setn)); //存强连通分量的点的个数 cou = sum = 0; // cou 是和node联系起来的。sum是最后结果。 } void input() { int from,num,to; scanf("%d%d",&n,&m); while( m-- ) { scanf("%d%d",&from,&num); while( num-- ) { scanf("%d",&to); node[cou].num = to; node[cou].next = v[from]; v[from] = &node[cou++]; } } } void Transpose(vote **v,vote **ve)//转置,v转置成ve,算导上有课后题,O(E)的算法。 { int i; vote *head; for(i=1; i<=n; i++) if( v[i] != NULL ) { head = v[i]; while( head != NULL ) { node[cou].num = i; node[cou].next = ve[head->num]; ve[head->num] = &node[cou++]; head = head->next; } } } void DFS(vote **v,int s) // 第一次DFS,求完成时间。 { vote *head = v[s]; visited[s] = 1; while( head != NULL ) { if( !visited[head->num] ) DFS(v,head->num); head = head->next; } fin.push(s);//之所以用栈是因为方便呀,这个是升序存的,用的时候降序pop()就可以了。 //我自己想的哦~~~^ ^ } void SecDFS(vote **ve,int s,int ss)//第二次DFS。 { vote *head = ve[s]; visited[s] = 1; while( head != NULL ) { if( !visited[head->num] ) { sou++; //记录里面点的个数,也就是强连通分量里面点的个数。 pre[head->num] = ss;//将这些点的前缀都赋值成这个强连通分量的统一的一个数。 SecDFS(ve,head->num,ss); } else tmp[ind++] = head->num;// 如果已经访问过了,标记下,下面要用。 head = head->next; } } int main() { int ncases,i,num; scanf("%d",&ncases); while( ncases-- ) { init(); input(); for(i=1; i<=n; i++) // 第一次DFS。 if( v[i] != NULL && !visited[i] ) DFS(v,i); Transpose(v,ve); //转置 memset(visited,0,sizeof(visited)); memset(pass,0,sizeof(pass)); while( !fin.empty() )// 按完成时间降序第二次DFS。 { num = fin.top(); fin.pop(); if( !visited[num] ) { sou = 1; ind = 0; pre[num] = num; // 这个就是把在同一个强连通分量里的点标记成num SecDFS(ve,num,num); setn[num] = sou;//存下这个强连通分量点的个数。 if( sou > 1 ) // 如果大于1,就是这个强连通分量多于一个点,那么目前看来它是满足题意的。 for(i=0; i<ind; i++) pass[tmp[i]] = 1;//如果大于1,那么它扫描到的已经访问过的点标记下,下面有用。 } } for(i=1; i<=n; i++)//下面就是处理那些强连通分量里面点是1的。因为有些满足题意有些不行。 { if( setn[i] == 1 && pass[i] == 0 )// 如果本身为1,但是没有被其他强连通分量(点大于1)访问到 { //说明它在v中没有连接强连通分量(点大于1的),不能要它。 vote *head = ve[i]; //而且它连接的点也不符合题意了,就把它连接点的存的点给赋值为0。 setn[i] = 0; while( head != NULL ) { setn[pre[head->num]] = 0; head = head->next; } } if( setn[i] == 1 && pass[i] )//如果本身为1,被其他强连通分量(点大于1)访问到 { //如果本身没有连接其他的,那么他肯定没有投票(想象一下在v中的情况) vote *head = ve[i]; //那肯定不能要它。 if( head == NULL ) { setn[i] = 0; continue; } while( head != NULL ) { if( setn[pre[head->num]] <= 1 ) //如果它连接的有单独的,那肯定是,另一个不连它的。 { //因为如果另一个连它,那么他们就是一个强连通分量了。 setn[i] = 0; //那样的话setn不可能是1。 break; //所以它肯定不符合题意。 } head = head->next; } } } for(i=1; i<=n; i++) sum += setn[i]; printf("%d/n",sum); } return 0; }