拓扑排序,可达性统计

版权声明:转载请在原文附上源连接以及作者,谢谢~ 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的子节点。所有我们可以先求出拓扑序最后面的点的可达点数,再依次算出前面的可达点数。设 f ( x ) f(x) 表示x的可达点数, S ( x ) S(x) 表示x的子节点的集合,则 f ( x ) = x f ( y ) , y S ( x ) f(x)=x∪f(y),y∈S(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;
} 

猜你喜欢

转载自blog.csdn.net/weixin_39778570/article/details/83860700