牛客 51011 可達性統計(拓撲排序,bitset)

Emiria發表於2020-08-07

牛客 51011 可達性統計(拓撲排序,bitset)

題意:

給一個 n個點,m條邊的有向無環圖,分別統計每個點出發能夠到達的點的數量(包括自身) \(n,m\le30000\).

樣例:

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

題解:

想要統計每個點能夠出發到達的點數量,如果一個一個點來搜尋計算的話,那麼複雜度將會變成 \(O(n^2)\),所以我們要換個角度思考,在訪問每一個點的時候,考慮由哪個點可以到達它,所以我們可以反向建邊,按照圖拓撲排序的順序進行訪問,將自身的貢獻傳遞給自己的臨點。

但是計算貢獻的時候,要考慮重複的計算,如下圖(已經是反向建邊了),D點訪問過後,A和C點的貢獻都為2,如果再一次 訪問完A和C後,B的貢獻就會變成5,顯然的多計算了一次D的貢獻。

​ 在這裡我們可以引入二進位制位來避免這種情況發生,也就是用一個數字的二進位制數來表示一個點的貢獻,這個二進位制數第i位為 \(i\),則代表這個點可以到達 \(i\),設上圖的A,B,C,D點分別為點
1,2, 3,4.那麼起始的點1,2,3,4的貢獻可以記錄為 2,4,8,16.其實是他們的二進位制位的第一位,第二位,第三位,第四位設位1了,那麼點A的貢獻將為 \(val_A | val_D\)=2|16 =18,C的貢為
\(valD|valC\)=16|8 =24.這樣點B的貢獻將為\(valA|valB|valC\)=18 | 24 | 4=30.這代表著B點可以到達第一個點,第二個,第三個點,第四個點。由於或運算的關係,D的貢獻不會重複計算。
不過這裡還有一個問題是由於資料規模的原因我們不能直接用數字來表示二進位制位的貢獻,因此我們要引入bitset。

bitset

bitset是C++提供的一個模板類,原型為 template<size_t N>class bitset。引數是bitset的二進位制位的個數。

bitset<6>B;//這裡我們定義了一個長度為40的bitset,它的每一位都是bool型別,佔記憶體1bit
B[2]=1;//每一個位置都可以通過下標來訪問以及進行修改。
bitset<6>C(string("101"));//我們還可以用一個只包含0和1字元的string類來初始話這個bitset,
//這裡bitset的值依次為000101.是string的長度超過bitset的長度,字串後面的字元會被捨棄掉。
C.count();//輸出C種為1的bool元素的個數。這裡結果為2.
C.reset();//將C的所有的位數全部置為1.
B = B|C;//同時支援  或,與,異或 這種位運算。要保證他們的長度要相同。

​有了bitset的這些操作後,思路就很明顯了,按照拓撲排序的順序訪問,每一次用bitset來傳遞貢獻,最後輸出每個bitset種為1的bool元素的個數。

主要Code:

struct P{int x;int y;P(){}P(int _x,int _y):x(_x),y(_y){}};
vector<int>ege[maxn];
bitset<maxn>cnt[maxn];
set<P>S;
int in[maxn]={0};
bool operator<(const P a,const P b){//
    if(a.x==b.x)return a.y<b.y;
    return a.x<b.x;
}
void solve(){
    int n,m;scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++){
        int x,y;scanf("%d %d",&x,&y);
        if(S.count(P(x,y)))continue;//將重複元素篩掉。
        S.insert(P(x,y));
        ++in[x];
        ege[y].push_back(x);
    }
    for(int i=1;i<=n;i++)cnt[i][i]=1;//將第i個bitset的第i位初始化1.
    queue<int>q;
    for(int i=1;i<=n;i++){
        if(in[i]==0)q.push(i);
    }
    while(!q.empty()){
        int u = q.front();
        q.pop();
        for(int v:ege[u]){
            --in[v];
            cnt[v] = cnt[v]|cnt[u];//用或運算將u的貢獻傳遞給v。
            if(in[v]==0)q.push(v);
        }
    }
    for(int i=1;i<=n;i++)printf("%d\n",cnt[i].count());
}

相關文章