拓撲排序,可達性統計
阿新 • • 發佈:2018-11-21
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;
}