有N(N<=10000)头牛,每头牛都想成为most poluler的牛,给出M(M<=50000)个关系,如(1,2)代表1欢迎2,关系可以传递,但是不可以相互,即1欢迎2不代表2欢迎1,但是如果2也欢迎3那么1也欢迎3.
给出N,M和M个欢迎关系,求被所有牛都欢迎的牛的数量。
用强联通分量做,求连通分量我用的是Kosaraju算法。
首先求出联通分量的个数,然后依次求各个联通分量的出度,如果仅有一个连通分量出度为0则这个联通分量内的点的个数就是答案;如果有多于一个的联通分量的出度为0,则说明此有向图肯定不连通。因此直接输出0。
可以用Tarjan算法或者kosaraju算法来求解,但是俩算法太难理解透彻了,直接扔上代码回头整理个模版出来吧
Tarjan
#include<iostream> using namespace std; int dfn[10010],low[10010],times,C,s[10010],S[10010],top; bool in[10010]; struct LIST { int v; LIST *next; }; LIST *head[10010],*rear[10010]; int chudu[10010]; void tarjan(int v) /*tarjan求图的强连通分量*/ { dfn[v]=low[v]=++times;//dfn存储时间戳,就是被dfs搜到的次序 , S[top++]=v; //Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号 in[v]=true; for(LIST *p=head[v];p!=NULL;p=p->next) if(!dfn[p->v]) { tarjan(p->v); if(low[p->v]<low[v]) low[v]=low[p->v]; } else if(in[p->v]&&low[p->v]<low[v]) low[v]=low[p->v]; if(low[v]==dfn[v]) { C++; do { v=S[--top]; in[v]=false; s[v]=C; /*缩图,标记属于同一强连通分量的点,点v属于第C个连通分图*/ }while(low[v]!=dfn[v]); } } int main() { int n,m,i,a,b; while(cin>>n>>m) { memset(head,0,sizeof(int)*10010); memset(rear,0,sizeof(int)*10010); memset(s,0,sizeof(int)*10010); top=0; for(i=0;i<m;i++) /*邻接表存储关系图*/ { scanf("%d%d",&a,&b); if(rear[a]!=NULL) { rear[a]->next=new LIST; rear[a]=rear[a]->next; } else head[a]=rear[a]=new LIST; rear[a]->next=NULL; rear[a]->v=b; } times=0; C=0; memset(dfn,0,sizeof(int)*10010); memset(low,0,sizeof(int)*10010); memset(in,0,sizeof(bool)); for(i=1;i<=n;i++) if(!dfn[i]) tarjan(i); if(C==1) /*如果只有一个强连通分量,则输出n*/ { cout<<n<<endl; continue; } memset(chudu,0,sizeof(chudu)); for(i=1;i<=n;i++) /*计算缩图后每个点的出度*/ for(LIST *p=head[i];p!=NULL;p=p->next) if(s[i]!=s[p->v]) chudu[s[i]]=1; a=b=0; for(i=1;i<=C;i++) if(chudu[i]) a++; else b=i; if(a==C-1) /*统计出度总数是否为C-1*/ { a=0; /*如果出度总数为C-1,则统计出度为0的连通分量包含点的个数*/ for(i=1;i<=n;i++) if(s[i]==b) a++; cout<<a<<endl; } else /*如果出度总数不为C-1,则无解,输出0*/ cout<<'0'<<endl; } return 0; }
kosaraju
#include <cstdio> #include <cstring> #include <vector> using namespace std; const int MAX = 10005;//v为原图 ,rv为所有边逆向后的新图 vector<int> v[MAX], rv[MAX], s;//s记录第一遍dfs后各点的结束时间:最晚结束的在栈顶 int num[MAX], tree_n[MAX], cnt;// num[v]点v所属连通分量的编号 * tree_n[i]第i个连通分量内点的个数 bool mark[MAX]; void dfs_1(int x) { mark[x] = 1; for (int i = 0; i < v[x].size(); ++i) if (!mark[v[x][i]]) dfs_1(v[x][i]); s.push_back(x); } void dfs_2(int x) { num[x] = cnt; //点v所属连通分量的编号为cnt ++tree_n[cnt]; mark[x] = 1; for (int i = 0; i < rv[x].size(); ++i) if (!mark[rv[x][i]]) dfs_2(rv[x][i]); } void cal(int n) { memset(mark, 0, sizeof(mark)); for (int i = 1; i <= n; ++i) { for (int j = 0; j < v[i].size(); ++j) { int x = v[i][j]; if (num[i] != num[x]) //如果ij相连的边不再同一个连通分量 mark[num[i]] = 1; //这个点i标记,出度不是0 } } int flag = 0, ans; for (int i = 1; i <= cnt; ++i) { if (!mark[i]) { ans = i; ++flag; } } if (flag == 1)printf("%d\n", tree_n[ans]); else printf("0\n"); } int main() {int a,b,n,m,i,j; scanf("%d%d",&n,&m); for (i = 0; i < m; ++i) { scanf("%d%d", &a, &b); v[a].push_back(b); rv[b].push_back(a);//反图存储 } for (i = 1; i <= n; ++i) if (!mark[i]) dfs_1(i); memset(mark, 0, sizeof(mark)); memset(tree_n, 0, sizeof(tree_n)); cnt = 0; //cnt记录连通分量的个数 for (i = s.size() - 1; i >= 0; --i) { if (!mark[s[i]]) { ++cnt; dfs_2(s[i]); } } cal(n); return 0; }