赞
踩
记 d i : i d_i:i di:i 在原图中的度数。
按照以下规则改写无向图为有向图:
「其实只需要定一个有序的规则即可,可以是度数小的指向大的,可以是编号大的指向小的,都不影响。」
显然,这个有向图同时也是个无环图。
证明:
假设存在一个环 ( 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} dx0≥dx1≥dx2≥...≥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 u→v,u→w,v→w。
考虑在 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 u→v 边上,显然为 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} outu≥outv>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; }
bitset
其实是一种二进制的优化。
只要想要的信息可以转化为被
0
/
1
0/1
0/1 表示出来,在空间不炸的情况下,bitset
自带
/
ω
/\omega
/ω 的复杂度。
将与
i
i
i 点直接相连的所有点对应在
i
i
i 的 bitset
中位置置为
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 ∑d≤m,d≤B,总共为 ∑ d 2 ≤ B ⋅ m \sum d^2\le B·m ∑d2≤B⋅m。
时间复杂度为 O ( n m ω + B ⋅ m ) O(\frac{nm}{\omega}+B·m) O(ωnm+B⋅m),空间复杂度就降为了 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-v\rightarrow w,u-x\rightarrow w u−v→w,u−x→w,且 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 ans←cntw,再 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; }
按照无向图的方法做,求出一个元环后直接随便选一个点开始左右两个方向都走一圈,看能不能回到开始点,就知道无向图上的元环能否用原有向图上的边构造出来。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。