版权声明:转载请在原文附上源连接以及作者,谢谢~ https://blog.csdn.net/weixin_39778570/article/details/83860700
ACM题集:https://blog.csdn.net/weixin_39778570/article/details/83187443
拓扑排序
把入度为0的点加入列队,这些点一点在拓扑排序的前面。遍历列队依次出队,出队的点加入拓扑序,把出队的点的儿子的入度都减少一,可以近似认为该父节点已经不在树结构中了,再把入度为0的点加入列队,循环遍历直到所有点都进入拓扑序,即列队为空。
/*拓扑排序 */
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int maxn = 1e6+5;
int nxt[maxn], head[maxn], ver[maxn],deg[maxn],tot;
void add(int x, int y){
ver[++tot] = y, nxt[tot]=head[x],head[x]=tot;
deg[y]++;// y的入度
}
int a[maxn],cnt;
int n,m;
void topsort(){ // 拓扑排序
queue<int> q;
for(int i=1; i<=n; i++){
if(deg[i]==0)q.push(i); // 加入入度为0的点
}
while(q.size()){
int x = q.front(); q.pop();
a[++cnt] = x;
for(int i=head[x]; i; i=nxt[i]){
int y = ver[i];
if(--deg[y]==0)q.push(y);
}
}
}
int main(){
cin>>n>>m; // 点数、边数
for(int i=1; i<=m; ++i){
int x, y;
scanf("%d%d",&x,&y);
add(x,y);
}
topsort();
for(int i=1; i<=cnt; i++){
printf("%d%c",a[i],i==cnt?'\n':' ');
}
return 0;
}
可达性统计
题目:http://contest-hunter.org:83/contest/0x20「搜索」例题/2101 可达性统计
题意:给定一张N个点M条边的有向无环图,分别统计从每个点出发能够到达的点的数量。N,M≤30000。
解法:先求出拓扑序,反向遍历拓扑序的每个点。对于拓扑序来说,假设x的y的前驱,那么x可能是y的父节点,但x一点不是y的子节点。所有我们可以先求出拓扑序最后面的点的可达点数,再依次算出前面的可达点数。设
表示x的可达点数,
表示x的子节点的集合,则
,可以使用二进制表示集和,1表示可达,集合并可用二进制或运算表示。
/* 可达性统计,f[x]表示x能到达的点的集合,利用二进制表示,1为能到达的点的集合,或运算可以使集合合并 */
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int maxn = 3e4+7;
int n,m;
int ver[maxn],head[maxn],nxt[maxn],tot;
int deg[maxn],top[maxn],cnt;
bool vis[maxn];
void add(int x,int y){
ver[++tot]=y,nxt[tot]=head[x],head[x]=tot;
deg[y]++; // y入度增加
}
void topsort(){
queue<int> q;
fo(i,1,n)if(deg[i]==0)q.push(i);
while(q.size()){
int x=q.front();q.pop();
top[++cnt]=x;
vis[x] = 1;
for(int i=head[x]; i; i=nxt[i]){
int y = ver[i];
if(vis[y])continue;
deg[y]--; // x已经在拓扑序前面了,儿子的入度都减少1
if(deg[y]==0)q.push(y);
}
}
}
bitset<maxn> f[maxn];
void solve(){
topsort();
for(int i=cnt; i>=1; i--){ // 从后往前拓扑序
int x = top[i];
f[x][x] = 1;
for(int j=head[x]; j; j=nxt[j]){
int y=ver[j]; // 记得...
f[x] |= f[y]; // 后续j已经计算好了, 或运算合并集合
}
}
fo(i,1,n)printf("%d\n", f[i].count());
}
int main(){
scanf("%d%d",&n,&m);
int x,y;
fo(i,1,m){
scanf("%d%d",&x,&y);
add(x,y);
}
solve();
return 0;
}
vector表示边
/*达性统计,f[x]表示x能到达的点的集合,利用二进制表示,1为能到达的点的集合,或运算可以使集合合并 */
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int maxn = 3e4+7;
int n,m;
vector<int> G[maxn];
int deg[maxn],top[maxn],cnt;
bitset<maxn> f[maxn];
void topsort(){
queue<int> q;
fo(i,1,n)if(deg[i]==0)q.push(i);
while(!q.empty()){
int u=q.front();q.pop();
top[++cnt]=u;
for(int v:G[u]){
deg[v]--;
if(deg[v]==0)q.push(v);
}
}
}
void solve(){
topsort();
for(int i=cnt; i>=1; i--){
int u = top[i];
f[u][u] = 1;
for(int v:G[u]){
f[u] |= f[v];
}
}
fo(i,1,n)printf("%d\n",f[i].count());
}
int main(){
scanf("%d%d",&n,&m);
int u,v;
fo(i,1,m){
scanf("%d%d",&u,&v);
G[u].push_back(v);
deg[v]++;
}
solve();
return 0;
}