给定一张N个点M条边的有向无环图,分别统计从每个点出发能够到达的点的数量。
输入格式
第一行两个整数N,M,接下来M行每行两个整数x,y,表示从x到y的一条有向边。
输出格式
输出共N行,表示每个点能够到达的点的数量。
数据范围
1≤N,M≤30000
输入样例:
10 10
3 8
2 3
2 5
5 9
5 9
2 3
3 9
4 8
2 10
4 9
输出样例:
1
6
3
3
2
1
1
1
1
1
思路: 根据拓扑排序后序列的特点(每个点原来的有向边所指的方向是一致的),所以先拓扑排序,然后用一个二进制数的每一位的0和1两个状态表示当前点到达的点,当前点能到达点的编号的位置就置为1,否则为0;最后从后往前遍历每个点表示的二进制数按位或上它能到的点,这样二进制中最后1的个数就是这个点能到的点的个数
完整代码:
#include <iostream>
#include <cstring>
#include <bitset>
#include <queue>
using namespace std;
const int maxn=3e4+5;
bitset<maxn>p[maxn];
int n,m,d[maxn],seq[maxn],e[maxn],net[maxn],cnt,head[maxn];
void addEdge(int u,int v)
{
e[cnt]=v;
net[cnt]=head[u];
head[u]=cnt++;
}
void topSort()
{
queue<int>q;
for(int i=1;i<=n;i++){
if(!d[i])
q.push(i);
}
while(q.size()){
int t=q.front();
q.pop();//拓扑排序每次先取出一个入度为0的点,并删掉这个点和它所连的边
seq[cnt++]=t;//加入到拓扑排序的序列中,这样最后这个序列的特征是每个点原来的有向边所指的方向是一致的
for(int i=head[t];~i;i=net[i]){
int k=e[i];
if(--d[k]==0)//入度为0时,如队列,相当于准备删掉这个点
q.push(k);
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
memset(head,-1,sizeof head);
cin>>n>>m;
while(m--){
int x,y;
cin>>x>>y;
addEdge(x,y);
d[y]++;//y的入度++
}
cnt=0;
topSort();//拓扑排序
for(int i=n-1;i>=0;i--){//根据拓扑排序序列的特点,从后往前每个点按位或上它能到的点,这样二进制中最后1的个数就是这个点能到的点的个数
int j=seq[i];
p[j][j]=1;//编号为j的点则其二进制数的第j位就置为1(表示能到达自身)
for(int k=head[j];~k;k=net[k]){
p[j]|=p[e[k]];//二进制数按位或
}//如:一开始最后一个点没有能到的点,所以=00...01,假设倒数第二个点能到最后一个点则00...10|00...01=00...11
}
for(int i=1;i<=n;i++){
cout<<p[i].count()<<endl;
}
return 0;
}