【题解】P8519 图论 连通性 思维 复杂度分析

很奇怪的题,好像见过类似的套路但完全想不到。和同学讨论了好久……


考虑直接求出每个点能到达的形状显然是不太能低于 $O(nm)$ 的,这个询问的信息一定有所隐含。

将原图的互相到达关系形容为一个有向图,那么可以到达点数最少的集合一定是将这个有向图强连通分量缩点后,某些出度为 $0$ 的强连通分量中的点。

称这个有向图中的强连通的子图为强连通集合,即某个集合使得里面的点可以两两通过拿某些钥匙互相到达的一个集合。

统计能够到达点数最少的位置集合,考虑能够到达点数最少的位置,如果某个强连通的集合 $S$ 可以到达强连通的集合 $T$,那么我们没有必要统计 $S$,只用统计 $T$ 所在的强连通集合,如下:

  • 如果 $T$ 不能够到达 $S$,那么 $T$ 能够到达的点的个数严格小于 $S$,不用考虑 $T$。
  • 如果 $T$ 能够到达 $S$,那么 $T$ 和 $S$ 在同一个强连通集合中。

所以我们考虑维护可能成为答案的强连通集合,然后对这些集合进行扩展,如果一个集合 $S$ 能够到达另一个集合 $T$,那么我们便将这个 $S$ 设为“可以到达 $T$”的集合,然后不再统计它,到达关系可以用并查集完成传递。

每次选择一个目前不能到达其它集合的集合,判断它是否到达一个不连向自己的集合,如果能到达 $T$,将它设为能到达 $T$的集合。如果不能到达,那么所在集合就被封闭住了,不能再往外走,我们统计这个集合的大小即可(它是强连通的,因为所统计的其它集合都属于能连向自己的集合)。过程 BFS 一遍,到达第一个不在同一个集合里的点停下来,复杂度就是正确的。

其实抽象一点我们干的事情的本质是这样的:

  • 同样将到达关系建出一个有向图,我们要求出所有缩点后出度为 $0$ 的强连通分量。

  • 维护一个有向森林,边从叶子指向根,用这个有向森林来维护不完全的可达关系,因为我们只关心根,不关心其它点。

  • 每次拓展拿森林的根试图向外拓展:寻找一个能够连接到的,和自己不在同一棵树里的点。

  • 如果拓展成功,那么连接这个点和它能够到达的另一个森林,我们不在乎这些点和连向的森林的其它点的连通性,只在乎这些点都可以到达连向的森林的根。

  • 如果根拓展失败,那么根一定对应着一个没有出度的强连通分量:它能到达,的点全部能够到达它,找出这个强连通分量。

  • 使用类似 Boruvka 的每层拓展一次的方法(被拓展到的集合的大小改变了,为保证复杂度我们在本轮不再拓展),需要拓展的点数每次拓展后至少减半:要么不再需要拓展,要么被一个拓展的点连接到,要么拓展成功,后两种会使得这个森林增加至少点数一半的边数,所以复杂度是 $O((n+m)\log n)$ 的。

  • 这样做一定是正确的,因为最后任何一个没有出度的强连通分量一定存在一个代表的根,我们一定会在统计这个根对应的强连通分量处统计到,如果存在多余一个根对应着这个强连通分量,那么就代表森林的根之间还可以相互到达,还需要拓展。

简单,可读的代码:

#define rep(i,x,y) for(int i=x;i<y;++i)
using pi = pair<int,int> ;
using vi = vector<int> ;
int n , m , mp = 1e9; 
bool flg ;
vector < pi > ed[N] ; 
vi fa , r , u , v , c , wait[N] , ans , res , vis , closed; 
vector < bool > key; 
int find(int x) {
    while(x != fa[x]) x = fa[x] = fa[fa[x]] ;
    return x; 
}
void bfs (int s) {
    res.clear( ) ; 
    vi vis_key , vis_edg ; queue<int> q; 
    q.emplace(s) ;
    bool flag = 1;
    while(q.size( )) {
        auto u = q.front( ) ; q.pop( ) ;
        if(find(u) != s) {
            fa[s] = find(u) ;
            vis[find(u)] = 1;
            flag = 0 ;
            break;
        }
        if(vis[u]) continue;    vis[u] = 1; 
        res.emplace_back(u) ;
        for(int v:wait[r[u]]) q.emplace(v) ; wait[r[u]].clear( ) ;
        key[r[u]] = 1 , vis_key.emplace_back(r[u]) ;
        for(auto[v,w]:ed[u]) {
            if(key[w]) q.emplace(v) ; 
            else {
               if(wait[w].empty( )) vis_edg.emplace_back(w);
                wait[w].emplace_back(v);
            }
        }
    }   
    for(int k:vis_key) key[k] = 0 ;
    for(int w:vis_edg) wait[w].clear( ) ;
    if(flag) {
        if((int) res.size( ) == mp) for(int e:res) ans.emplace_back(e) ;
        if((int) res.size( ) < mp) ans = res , mp = res.size( ) ;
        closed[find(s)] = 1; 
        for(int p:res) vis[p] = 0 ;
    }
}
vi find_reachable(vi _r, vi _u,vi _v, vi _c) {
    r = _r , u = _u , v = _v , c = _c ;
    n = r.size( ) , m = u.size( ) ;
    vi ret(n , 0) ;
    fa.resize(n);iota(All(fa) , 0) ;
    closed.resize(n , 0) ; 
    key.resize(n) ;
    rep(i,0,m) {
        ed[u[i]].emplace_back(v[i] , c[i]) ;
        ed[v[i]].emplace_back(u[i] , c[i]) ;
    }
    while(1) {
        bool expand = 0 ;   
        vis.clear( ) , vis.resize(n , 0) ;
        rep(i,0,n) {
            if(!vis[i] && find(i) == i && !closed[i]) bfs(i) , expand = 1; 
        }
        if(!expand) break; 
    }
    for(int v:ans) ret[v] = 1 ;
    return ret ;
}
posted @ 2023-09-12 21:33  寂静的海底  阅读(2)  评论(0编辑  收藏  举报  来源