1. 程式人生 > >CH 2101 - 可達性統計 - [BFS拓撲排序+bitset狀壓]

CH 2101 - 可達性統計 - [BFS拓撲排序+bitset狀壓]

題目連結:傳送門

描述

給定一張N個點M條邊的有向無環圖,分別統計從每個點出發能夠到達的點的數量。N,M≤30000。

輸入格式

第一行兩個整數N,M,接下來M行每行兩個整數x,y,表示從x到y的一條有向邊。

輸出格式

共N行,表示每個點能夠到達的點的數量。

樣例輸入

10 10
3 8
2 3
2 5
5 9
5 9
2 3
3 9
4 8
2 10
4 9

樣例輸出

1
6
3
3
2
1
1
1
1
1

 

題解:

首先,如果用 $f(x)$ 代表從點 $x$ 出發所能到達的所有點的集合,應有如下公式:

$f(x) = {x} \cup (\bigcup_{edge(x,y)}f(y))$

也就是說我們可以通過某種遞推方式,遞推出所有點的 $f(x)$。

由此想到有向無環圖的拓撲序(對於圖中任意一條有向邊 $(x,y)$,在該序列中 $x$ 都出現在 $y$ 之前),對有向無環圖的拓撲序逆向遍歷計算,正好可以正確求出每個點的 $f(x)$。

另外, 我們還可以用狀壓的方式來儲存 $f(x)$,也比較方便轉移和儲存,這裡我們用bitset容器來做狀壓。

 

關於bitset:

 bitset<1000> num; 相當於定義了一個1000位的二進位制數,其 $1$ 位佔用 $1$ 個bit,也就是說 $8$ 位佔用一個Byte。

由於估計時間複雜度是我們一般以 $32$ 位整數的運算次數為基準,因此 $n$ 位的bitset執行一次位運算的時間複雜度可以視作 $n/32$。

bitset支援的位運算有按位取反“~”、按位與“&”、按位或“|”、按位異或“^”、左移“<<”、右移“>>”(均用 $0$ 填充),還可以比較是否相等“==”和“!=”。

bitset支援 num[k] 這種形式進行取值或者賦值。根據上面的定義,範圍為 num[0] 到 num[999] 。

bitset還支援:set()全部置 $1$、reset()全部置 $0$、count()統計 $1$ 的數目、any()查詢是否存在 $1$、none()查詢是否沒有 $1$。

 

AC程式碼:

#include<bits/stdc++.h>
using namespace std;
const int maxn=30000+5;
int n,m;
int indg[maxn];
vector<int> e[maxn];
bitset<maxn> f[maxn];

vector<int> topo;
void TopoSort()
{
    topo.clear();
    queue<int> Q;
    for(int i=1;i<=n;i++) if(indg[i]==0) Q.push(i);
    while(Q.size())
    {
        int u=Q.front(); Q.pop();
        topo.push_back(u);
        for(auto v:e[u]) if(--indg[v]==0) Q.push(v);
    }
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    cin>>n>>m;
    memset(indg,0,sizeof(indg));
    for(int i=1,x,y;i<=m;i++) cin>>x>>y, indg[y]++, e[x].push_back(y);

    TopoSort();
    for(int i=topo.size()-1;i>=0;i--)
    {
        int x=topo[i];
        f[x].reset(), f[x][x]=1;
        for(auto y:e[x]) f[x]|=f[y];
    }
    for(int i=1;i<=n;i++) cout<<f[i].count()<<endl;
}