有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;
}

浙公网安备 33010602011771号