受牛仰慕的牛(popular cows)
每头牛都有一个梦想:成为一个群体中最受欢迎的名牛!在一个有N(1<=N<=10,000)头牛的牛群中,给你M(1<=M<=50,000)个二元组(A,B),表示A认为B是受欢迎的。既然受欢迎是可传递的,那么如果A认为B受欢迎,B又认为C受欢迎,则A也会认为C是受欢迎的,哪怕这不是十分明确的规定。你的任务是计算被所有其它的牛都喜欢的牛的个数。
这道题直接考虑模拟是不行的,要考虑抽象模型。将仰慕关系建立成一个有向图,然后计算出强连通分量,缩点之后计算每个点的出度,如果有且只有一个点的出度为0,那么这头牛存在,否则不存在。
要注意的是,缩点之后输出答案的时候,输出的是这个点(强连通分量)所包含的点数。
这道题是老师拿给我练习强连通的练手题。在强连通的算法上可以看出我对细节的把握还是不纯熟。比如忘记赋值DFN和LOW数组,还有就是对题目的抽象模型的能力分析不熟,如果不是老师告诉我这是强连通的话我估计是没有办法知道这道题的正确解法的。
下附代码,同样不知道那时候的我在干什么。
#include <algorithm>
#include <iostream>
#include <fstream>
#include <cstring>
#include <cstdlib>
#include <cmath>
using namespace std;
ifstream fin("popular.in");
ofstream fout("popular.out");
struct ls
{
int nw;
int nxt;
};
ls qxx[];//链式前向星
int cows=,gx=,bian=,qlt=,cs=;
int qlts[]={};//每个强连通分量包含的点数
int DFN[]={},LOW[]={},tou[]={};
int outs[]={};//每个强连通分量的出度
int from[]={};//每个点所属的强连通分量
bool rz[]={};//每个点是否入栈
bool pd[][]={};//两个强连通分量之间是否相通
int zhan[]={},top=;
void add(int fr,int to);
void add2(int fr,int to);
void tarjan(int nw);
int main(void)
{
fin>>cows>>gx;
int a=,b=;
memset(tou,-,sizeof(tou));
for(int i=;i<=gx;i++)
{
fin>>a>>b;
add(a,b);
}
for(int i=;i<=cows;i++)
{
if(!DFN[i])tarjan(i);
}
int v=;
for(int i=;i<=cows;i++)
{
for(int j=tou[i];j>;j=qxx[j].nxt)
{
v=qxx[j].nw;
if(from[i]!=from[v]&&!pd[from[i]][from[v]])add2(from[i],from[v]);
}
}
int total=,ans=;
for(int i=;i<=qlt;i++)
{
if(outs[i]==)
{
total++;
ans+=qlts[i];
}
}
if(total==)fout<<ans;
else fout<<"";
return ;
} void add(int fr,int to)
{
bian++;
qxx[bian].nw=to;
qxx[bian].nxt=tou[fr];
tou[fr]=bian;
} void tarjan(int nw)
{
top++;
DFN[nw]=LOW[nw]=++cs;
zhan[top]=nw;
rz[nw]=true;
int v=;
for(int i=tou[nw];i>;i=qxx[i].nxt)
{
v=qxx[i].nw;
if(!DFN[v])
{
tarjan(v);
LOW[nw]=min(LOW[v],LOW[nw]);
}
else
{
if(rz[v]==true)
{
LOW[nw]=min(LOW[nw],DFN[v]);
}
}
}
if(DFN[nw]==LOW[nw])
{
qlt++;
do
{
v=zhan[top];
from[v]=qlt;
qlts[qlt]++;
rz[v]=false;
top--;
}while(nw!=v);
}
return;
} void add2(int fr,int to)
{
pd[fr][to]=true;
outs[fr]++;
return;
}