题意:每头牛都想成为牛群中的红人。给定N头牛的牛群和M个有序对(A,B)。(A,B)表示牛A认为牛B是红人。该关系具有传递性,所以如果牛A认为牛B是红人,牛B认为牛C是红人,那么牛A也认为牛C是红人。求被所有牛认为是红人的牛的总数。
题解:考虑以牛为顶点的有向图,对每个有序对(A,B)连一条从A到B的有向边。那么,被其他所有牛认为是红人的牛所对应的顶点,也就是从其他点都可达的顶点。虽然这可以通过从每个顶点出发搜索得到,但总的复杂度是O(NM),是不行的,必须考虑更为高效的算法,假设有两头牛A和B都被其他所有牛认为是红人,那么其所属的强连通分量内的所有牛都被其他所有牛认为是红人。由此,把图进行强连通分量分解后,至多有一个强连通分量满足题目的条件。
两种解法:kosareju / tarjan
一:kosaraju
按照此算法分解后,能够得到各个强连通分量拓扑排序后的顺序,唯一可以成为解的只有拓扑排序最后的强连通分量。所以在最后,只要检查这个强连通分量是否从所有顶点可达就好了。
附上代码:
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
const int max_v=1e4+50;
int V,m;
vector<int>g[max_v];
vector<int>rg[max_v];
vector<int>vs;
bool used[max_v];
int cmp[max_v];
void add_edge(int from,int to)
{
g[from].push_back(to);
rg[to].push_back(from);
}
void dfs(int v)
{
used[v]=true;
for(int i=0;i<g[v].size();i++){
if(!used[g[v][i]]){
dfs(g[v][i]);
}
}
vs.push_back(v);
}
void rdfs(int v,int k)
{
used[v]=true;
cmp[v]=k;
for(int i=0;i<rg[v].size();i++){
if(!used[rg[v][i]]){
rdfs(rg[v][i],k);
}
}
}
int scc()
{
memset(used,0,sizeof(used));
vs.clear();
for(int v=0;v<V;v++){
if(!used[v]){
dfs(v);
}
}
memset(used,0,sizeof(used));
int k=0;
for(int i=vs.size()-1;i>=0;i--){
if(!used[vs[i]]){
rdfs(vs[i],k++);
}
}
return k;
}
int main()
{
scanf("%d%d",&V,&m);
int u,v;
for(int i=0;i<m;i++){
scanf("%d%d",&u,&v);
add_edge(u-1,v-1);
}
int n=scc();
int num=0;
for(v=0;v<V;v++){
if(cmp[v]==n-1){
num++;
u=v;
}
}
memset(used,0,sizeof(used));
rdfs(u,0);
for(int v=0;v<V;v++){
if(!used[v]){
num=0;
break;
}
}
printf("%d\n",num);
return 0;
}
二:tarjan算法
思路:将n头奶牛的m个关系将会构建一个有向图,在图中强连通分量中的任意奶牛一定是被分量中的其他奶牛仰慕。所以问题就转化为在图中将强连通分量进行缩点,在形成的新图中,如果一个强连通分量集合的出度为零,说明这个集合被其他集合仰慕,而不仰慕其他的集合,所以如果在新图中集合出度为零的数目不大于1,则为出度为零集合中奶牛的数目,如果大于1,则出度为零集合之间没有仰慕关系的,所以结果为0.
附上代码:
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
const int maxn=10050;
int n,m;
vector<int>g[maxn];
int temper,top,dfn[maxn],low[maxn],zhan[maxn],used[maxn];
int cnt,num[maxn];
vector<int>lt[maxn];
int d[maxn];
void tarjan(int u)
{
dfn[u]=low[u]=++temper;
used[u]=1;
zhan[top++]=u;
for(int i=0;i<g[u].size();i++){
int temp=g[u][i];
if(!dfn[temp]){
tarjan(temp);
low[u]=min(low[u],low[temp]);
}else if(used[u]){
low[u]=min(low[u],dfn[temp]);
}
}
int j;
if(dfn[u]==low[u]){
cnt++;
do{
j=zhan[--top];
used[j]=0;
lt[cnt].push_back(j);
num[j]=cnt;
}while(u!=j);
}
}
void solve()
{
for(int i=0;i<n;i++){
if(!dfn[i]){
tarjan(i);
}
}
}
int main()
{
scanf("%d%d",&n,&m);
int u,v;
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
g[u-1].push_back(v-1);
}
solve();
for(int i=0;i<n;i++){
for(int j=0;j<g[i].size();j++){
int t=g[i][j];
if(num[i]!=num[t]){
d[num[i]]++;
}
}
}
int pos=-1;
int ans=0;
for(int i=1;i<=cnt;i++){
if(d[i]==0){
ans++;
pos=i;
}
}
if(ans==1){
printf("%d\n",lt[ans].size());
}else{
printf("0\n");
}
return 0;
}