ACWing164可达性统计(拓扑排序+bitset)
程序员文章站
2022-07-12 16:39:25
...
题目传送门:https://www.acwing.com/problem/content/166/
题目大意:给定一个有向无环图,问你每个点能到达的点的数量。(N,M<= 30000)。
分析:
一、初看这可是一个妥妥的图的遍历的问题,枚举每个点,然后从每个点bfs或dfs一遍,看能访问到几个点计数即可,如下代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 30000+6;
struct node{
int v,next;
};
node edge[MAXN];
int head[MAXN],cnt,n,m,s;
bool vis[MAXN];
void addEdge(int u,int v){
edge[++cnt] = (node){v,head[u]};
head[u] = cnt;
}
void dfs(int u){
for(int i = head[u]; i; i = edge[i].next){
int v = edge[i].v;
if(!vis[v]){
s ++;
vis[v] = true;
dfs(v);
}
}
}
int main(){
int x,y;
cin >> n >> m;
for(int i = 1;i<= m; i++){
cin >> x >> y;
addEdge(x,y);
}
for(int i =1; i<= n; i++){
s = 1;
memset(vis,false,sizeof(vis));
dfs(i);
cout << s << endl;
}
return 0;
}
10分钟写完程序,没有仔细分析数据量,然后就TLE。
那么暴力搜不行了。如何是好?
二、进一步看题,注意一个重要信息,这个图是有向无环图(DAG),如果有一条边(u - > v),从u出发的能够到达的点,是从u所能到达的所有后继节点v出发能到达的点的和,再加上自己本身就是题解。那么我们需要先算出v所能达到的所有点,依此下去,我们发现,需要从图的路径的末端往起点推,如何保证点的枚举顺序是这样的呢?拓扑排序可以满足这一点,将图进行拓扑排序后,倒序后便是这样的特性。
另外我们如何u-v求所有v的能到达的点呢。我们可以采用一个N位二进制数存储s[x],如果第i位为1表示x能到i点。那么最后只需要看s[x]里存储的二进制里有多少个1既能知道能到多少个点。运用C++STL里的bitset可以帮助我们存储。
AC代码如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 30000+6;
struct node{
int v,next;
};
queue<int>q;
node edge[MAXN];
int a[MAXN],head[MAXN],cnt,cnts,n,sum ,m,rudu[MAXN];
bool vis[MAXN];
bitset<MAXN> s[MAXN];
void addEdge(int u,int v){
edge[++cnt] = (node){v,head[u]};
head[u] = cnt;
}
void topSort(){
for(int i = 1;i <= n; i++){
if(rudu[i] == 0) q.push(i);
}
while(!q.empty()){
int u = q.front();
a[++cnts] = u;
q.pop();
for(int i = head[u]; i; i= edge[i].next){
int v = edge[i].v;
rudu[v] --;
if(rudu[v] == 0) q.push(v);
}
}
}
void calc(){
for(int i = cnts; i; i--){
int u = a[i];
s[u][u] = 1;//将第u位置为1
for(int j = head[u]; j; j = edge[j].next){
int v = edge[j].v;
s[u] = s[u] | s[v];
}
}
}
int main(){
int x,y;
cin >> n >> m;
for(int i = 1;i<= m; i++){
scanf("%d%d",&x , &y);
addEdge(x,y);
rudu[y] ++;
}
topSort();
calc();
for(int i = 1;i<= n;i++){
printf("%d\n" , s[i].count());
}
return 0;
}