当前位置:   article > 正文

【学习笔记】无向图、有向图的三元环、四元环计数问题(根号分支+bitset)_有向图三元环计数

有向图三元环计数

无向图三元环计数问题

根号分治

d i : i d_i:i di:i 在原图中的度数。

按照以下规则改写无向图为有向图:

  • 对于一条边 u , v u,v u,v
    • 如果两点度数不同,则度数大的指向度数小的。
    • 如果两点度数相同,则编号小的指向编号大的。

「其实只需要定一个有序的规则即可,可以是度数小的指向大的,可以是编号大的指向小的,都不影响。」

显然,这个有向图同时也是个无环图。

证明:

假设存在一个环 ( x 0 , x 1 , x 2 , . . . , x k ) (x_0,x_1,x_2,...,x_k) (x0,x1,x2,...,xk)

则有 d x 0 ≥ d x 1 ≥ d x 2 ≥ . . . ≥ d x k d_{x_0}\ge d_{x_1}\ge d_{x_2}\ge ...\ge d_{x_k} dx0dx1dx2...dxk

d x 0 = . . . = d x k d_{x_0}=...=d_{x_k} dx0=...=dxk

所以 x 0 < x 1 < x 2 < . . . < x k < x 0 x_0<x_1<x_2<...<x_k<x_0 x0<x1<x2<...<xk<x0,矛盾。

所以三元环 ( u , v , w ) (u,v,w) (u,v,w) 在新图中的边一定是: u → v , u → w , v → w u\rightarrow v,u\rightarrow w,v\rightarrow w uv,uw,vw

考虑在 u u u 点记录这个三元环的贡献。

将所有与 u u u 直接相连的点打上标记,再从中枚举点作 v v v ,枚举与 v v v 直接相连的点 w w w ,检查 w w w 是不是 u u u 指向的点即可。

时间复杂度为 O ( m m ) O(m\sqrt{m}) O(mm )

证明:

将枚举 w w w 的复杂度记在 u → v u\rightarrow v uv 边上,显然为 o u t v out_v outv 。「 o u t v : v out_v:v outv:v 的出度」

则总复杂度为 ∑ ( u , v ) ∈ E o u t v + ∑ u o u t u \sum_{(u,v)\in E}out_v+\sum_{u}out_u (u,v)Eoutv+uoutu

  • o u t v < m out_v<\sqrt{m} outv<m ,则复杂度为 O ( m m ) O(m\sqrt{m}) O(mm )

  • o u t v > m out_v>\sqrt{m} outv>m ,因为有向图的连边规则,有 o u t u ≥ o u t v > m out_u\ge out_v>\sqrt{m} outuoutv>m

原无向图的总边数为 m m m,所以这样的 ( u , v ) (u,v) (u,v) 仅有 m \sqrt{m} m 个,复杂度为 O m O\sqrt{m} Om

#include <bits/stdc++.h>
using namespace std;
#define maxn 100005
#define maxm 200005
vector < int > G[maxn];
int n, m;
int d[maxn], Eu[maxm], Ev[maxm], vis[maxn];

bool cmp( int x, int y ) {
    return d[x] == d[y] ? x < y : d[x] > d[y];
}

int main() {
    scanf( "%d %d", &n, &m );
    for( int i = 1;i <= m;i ++ ) {
        scanf( "%d %d", &Eu[i], &Ev[i] );
        d[Eu[i]] ++, d[Ev[i]] ++;
    }
    for( int i = 1;i <= m;i ++ )
        if( cmp( Eu[i], Ev[i] ) ) G[Eu[i]].push_back( Ev[i] );
        else G[Ev[i]].push_back( Eu[i] );
    int ans = 0;
    for( int i = 1;i <= n;i ++ ) {
        for( int j : G[i] ) vis[j] = i;
        for( int j : G[i] )
            for( int k : G[j] ) 
                if( vis[k] == i ) ans ++;
    }
    printf( "%d\n", ans );
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

bitset

bitset 其实是一种二进制的优化。

只要想要的信息可以转化为被 0 / 1 0/1 0/1 表示出来,在空间不炸的情况下,bitset 自带 / ω /\omega /ω 的复杂度。

将与 i i i 点直接相连的所有点对应在 i i ibitset 中位置置为 1 1 1,表示两点有边相连。

然后就直接枚举每条边,将边的两个端点的 bitset 取个 &,就得到了与两点均直接相连的所有点集合。

空间复杂度为 O ( n 2 ) O(n^2) O(n2) ,来自 bitset

时间复杂度为 O ( n m ω ) O(\frac{nm}{\omega}) O(ωnm)

如果有些题目空间比较卡,不能直接硬开。

其实是给每个点很大一段的 0 0 0 开完整 bitset 空间就会被大幅度浪费。

那么就可以设定一个阈值 B B B「分块的阈值」。

对于度数 > B >B >B 的点就开完整的 bitset,这种点不会超过 min ⁡ ( n , m B ) \min(n, \frac{m}{B}) min(n,Bm) 个。

度数 ≤ B \le B B 的点就直接暴力枚举连边,暴力判断。

所有度数 > B >B >B 的点最多会被 O ( m ) O(m) O(m) 条边枚举到,一次计算是 O ( m ω ) O(\frac{m}{\omega}) O(ωm),时间复杂度为 O ( n m ω ) O(\frac{nm}{\omega}) O(ωnm)

度数 ≤ B \le B B 的点的贡献是 d 2 d^2 d2,则有 ∑ d ≤ m , d ≤ B \sum_d\le m,d\le B dm,dB,总共为 ∑ d 2 ≤ B ⋅ m \sum d^2\le B·m d2Bm

时间复杂度为 O ( n m ω + B ⋅ m ) O(\frac{nm}{\omega}+B·m) O(ωnm+Bm),空间复杂度就降为了 O ( n m B ) O(\frac{nm}{B}) O(Bnm)

「根据具体题目的时空灵活选取阈值 B B B,在不甚了解三元环的根号分治时,bitset 这种优美的暴力来的快也好写/doge」

无向图四元环计数问题

同样按照三元环计数问题的方法来建立有向图,并给每一个点分配唯一的 r a n k rank rank 排名。

则一个四元环在新图中至少有一个,至多有两个度数为 2 2 2 的点。

这时会发现原图的四元环 ( u , v , w , x ) (u,v,w,x) (u,v,w,x) 在新建的有向图中会有两种情况。

  • u → v → w , u → x → w u\rightarrow v\rightarrow w,u\rightarrow x\rightarrow w uvw,uxw
  • u → v → w → x , u → x u\rightarrow v\rightarrow w\rightarrow x,u\rightarrow x uvwx,ux

发现这两种形式都可以由 两条无向边+两条有向边 形式表示。

所以一个四元环唯一的表示为: u − v → w , u − x → w u-v\rightarrow w,u-x\rightarrow w uvw,uxw,且 r a n k ( u ) rank(u) rank(u) 最小, r a n k ( w ) rank(w) rank(w) 最大,这样才能保证一个四元环不被反复计算。

枚举点 u u u,枚举其在原图的出边 v v v,枚举 v v v 的在新图上所有出边 w w w,且要满足 r a n k ( u ) < r a n k ( v ) < r a n k ( w ) rank(u)<rank(v)<rank(w) rank(u)<rank(v)<rank(w)

a n s ← c n t w ans\leftarrow cnt_w anscntw,再 c n t w + + cnt_w++ cntw++,最后 u u u 更换的时候,所有 c n t cnt cnt 都要清零。

在上述过程中, c n t w : w cnt_w:w cntw:w 的入度,且 r a n k ( w ) rank(w) rank(w) 最大 r a n k ( u ) rank(u) rank(u) 最小,那么当有两组 ( u , v 1 , w ) , ( u , v 2 , w ) (u,v_1,w),(u,v_2,w) (u,v1,w),(u,v2,w) 的时候代表找到了一个四元环。

虽然环有对称性,但这样处理后一个四元环只会从 u u u 算到 w w w 上。

时间复杂度为 O ( m m ) O(m\sqrt{m}) O(mm )。证明与三元环一样。

#include <bits/stdc++.h>
using namespace std;
#define maxn 100005
vector < int > G[maxn], E[maxn];
int n, m;
int d[maxn], id[maxn], rnk[maxn], cnt[maxn];

int main() {
    scanf( "%d %d", &n, &m );
    for( int i = 1, u, v;i <= m;i ++ ) {
        scanf( "%d %d", &u, &v );
        E[u].push_back( v );
        E[v].push_back( u );
        d[u] ++, d[v] ++, id[i] = i;
    }
    sort( id + 1, id + n + 1, []( int x, int y ) { return d[x] == d[y] ? x < y : d[x] > d[y]; } );
    for( int i = 1;i <= n;i ++ ) rnk[id[i]] = i;
    for( int i = 1;i <= n;i ++ ) for( int j : E[i] ) if( rnk[j] > rnk[i] ) G[i].push_back( j );
    int ans = 0;
    for( int i = 1;i <= n;i ++ ) {
        for( int j : E[i] ) for( int k : G[j] ) if( rnk[k] > rnk[i] ) ans += cnt[k], cnt[k] ++;
        for( int j : E[i] ) for( int k : G[j] ) cnt[k] = 0;
    } 
    printf( "%d\n", ans );
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

有向图三四元环计数问题

按照无向图的方法做,求出一个元环后直接随便选一个点开始左右两个方向都走一圈,看能不能回到开始点,就知道无向图上的元环能否用原有向图上的边构造出来。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/64614
推荐阅读
相关标签
  

闽ICP备14008679号