1. 程式人生 > >拓撲排序,可達性統計

拓撲排序,可達性統計

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;
}