P2341 [HAOI2006]受欢迎的牛
题目概述:给定一张有向图,求从任何节点出发都可以抵达的节点个数。
数据规模:N<=10000,M<=50000
思路:Tarjan算法预处理强连通分量,缩点处理,然后得到一张新图,且这张图为有向无环图,然后利用并查集查找从任何节点出发都可以抵达的节点个数即可,用一个数组查询父亲节点为i的个数,但是要注意unionn的操作时,fa[xx]=fy,而不是原来的样子,也就是说我们需要操作的是让边上一个节点与另一个节点的根节点直接连接,可以称之为伪unionn操作吧,然后在每一个符合题意的新图的大节点中分别去计算小节点个数(当然也可以预处理直接实现),最后输出。
代码:
#include<iostream>
using namespace std;
int i,j,n,m;
bool b[100001];
int color[100001],colorn;
int stack[100001],top;//数组模拟栈
int s,e,temp,index,ans,bb[100001];
int dfn[100001],low[100001];
int head[100001],rd[100001],fa[100001];
int gs[100001];
struct data
{
int v,u;
data *nxt;
}a[1000001];
int r()
{
char ch=getchar();
int ans=0;
while(ch<'0'||ch>'9')
{
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
ans*=10;
ans+=ch-'0';
ch=getchar();
}
return ans;
}
int ins(int ss,int ee)
{
temp++;
a[temp].nxt=&a[head[ss]];
a[temp].u=ss;
head[ss]=temp;
a[temp].v=ee;
}
int findd(int xx)
{
if(fa[xx]==xx) return fa[xx];
return fa[xx]=findd(fa[xx]);
}
void unionn(int xx,int yy)
{
int fy=findd(yy);
fa[xx]=fy;
}
void tarjan(int x)
{
dfn[x]=++index;
low[x]=index;
b[x]=1,bb[x]=1;
stack[++top]=x;
data *p=&a[head[x]];
while(p->v!=0)
{
int vv=p->v;
if(!dfn[vv])
{
tarjan(vv);
low[x]=min(low[x],low[vv]);
}
else if(b[vv])
{
low[x]=min(low[x],dfn[vv]);
}
p=p->nxt;
}
if(dfn[x]==low[x])
{
b[x]=0;
color[x]=++colorn;
while(stack[top]!=x)
{
color[stack[top]]=colorn;
b[stack[top--]]=0;
}
top--;
}
}
int main()
{
n=r(),m=r();
for(i=1;i<=n;i++)
rd[i]=1;
for(i=1;i<=m;i++)
{
s=r(),e=r();
ins(s,e);
}
for(i=1;i<=n;i++)
if(!bb[i])
tarjan(i);
for(i=1;i<=colorn;i++)
fa[i]=i;
for(i=1;i<=m;i++)
{
if(color[a[i].v]!=color[a[i].u])
unionn(color[a[i].u],color[a[i].v]);
}
for(i=1;i<=colorn;i++)
findd(i);
for(j=1;j<=colorn;j++)
for(i=1;i<=colorn;i++)
if(fa[i]==j)
gs[j]++;
for(i=1;i<=colorn;i++)
{
if(gs[i]==colorn)
for(j=1;j<=n;j++)
if(color[j]==i)
ans++;
}
cout<<ans;
}