【学习记录】环计数问题

环计数问题是一类比较有意思的问题。

无向图三元环计数

先从最简单的三元环考虑起。

最暴力的做法是以每一个点为起点大力枚举,但这样每一个环会被算 6 次,时间复杂度也很高。下面介绍的是一种“优化过的暴力算法”。

首先给点定义大小关系,这里的大小关系定义为二元组 ( deg i , i ) (\operatorname{deg}_i, i) 的大小关系,其中 deg i \operatorname{deg}_i 表示编号为 i i 的点的度。即如果 ( deg i , i ) < ( deg j , j ) (\operatorname{deg}_i, i) < (\operatorname{deg}_j, j) 那么 i < j i<j

按照这种大小关系可以构造一张 DAG,边 ( i , j ) (i, j) 表示给定的无向图中 i , j i, j 相连且按上述定义 i < j i < j 。然后在这张图上找环。具体的,分为三步:

  1. 枚举点 i i
  2. 枚举 i i 在 DAG 上连接的所有点 j j ,将其标记上 i i
  3. 标记完后枚举 j j 连接的所有点 k k ,如果其已经被标记上 i i ,那么 i , j , k i, j, k 构成一个环。

这种做法的原理在于,将无向图上的三元环 A , B , C A, B, C 转换成为一个 DAG 上的结构 A B , A C , B C A\rightarrow B, A \rightarrow C, B\rightarrow C ,且通过定义某种“序”强制规定 A A B , C B, C 都要小。这样只能通过枚举 A A 统计这个环,且由于定了 B , C B, C 的大小关系使得这个环只能被统计一次。

可以证明,这一算法的时间复杂度为 O ( E 1.5 ) O(|E|^{1.5})

本题为例,给出算法实现:

int n, m, du[100005] = {0};
int to[200005], nxt[200005], at[100005] = {0}, cnt = 0;
int to2[200005], nxt2[200005], at2[100005] = {0}, cnt2 = 0;
int mk[100005] = {0};
inline int cmp(int i, int j){
    return (du[i] == du[j] ? i < j: du[i] < du[j]);
}
void init(){
    // 构建 DAG 的过程
    n = read(), m = read();
    for (int i = 1; i <= m; ++i){
        int u = read(), v = read();
        to[++cnt] = v, nxt[cnt] = at[u], at[u] = cnt;   // 只保存单向
        ++du[u], ++du[v];
    }
    for (int i = 1; i <= n; ++i)
        for (int j = at[i]; j; j = nxt[j]){
            if (cmp(i, to[j])) 
                to2[++cnt2] = to[j], nxt2[cnt2] = at2[i], at2[i] = cnt2;
            else
                to2[++cnt2] = i, nxt2[cnt2] = at2[to[j]], at2[to[j]] = cnt2;
        }
}
void solve(){
    // 正式求解
    int ans = 0;
    for (int i = 1; i <= n; ++i){
        for (int j = at2[i]; j; j = nxt2[j])
            mk[to2[j]] = i;
        for (int j = at2[i]; j; j = nxt2[j]){
            int v = to2[j];
            for (int t = at2[v]; t; t = nxt2[t]){
                if (mk[to2[t]] == i)
                    ++ans;
            }
        }
    }
    printf("%d\n", ans);
}

个人猜测连边按 i < j i<j 或者 i > j i>j 复杂度都是对的,但不知道怎么证明。

无向图四元环计数

四元环计数可以套用三元环的思路,即也采用定序的方法。

假设我们希望找的四元环是 A , B , C , D A, B, C, D ,并且仍然要求这个环只能在对最小的 A A 统计时被计算一次。那么在 DAG 上有 A B , A C A\rightarrow B, A \rightarrow C 。然后会发现 D D B , C B, C 的大小情况不明确,可以介于两者之间,也可以比两者都大或者小。这意味着应当对所有和 B , C B, C 连接的无向边都予以考虑。

由此,我们可以使用和之前类似的方法,只不过这次相当于把路径拆成了 A , B , D A, B, D A , C , D A, C, D 进行统计。需要一个辅助数组 c n t [ D ] cnt[D] 记录以 D D 为结尾的这样长度为 2 的路径的数目。

  1. 枚举点 i i
  2. 枚举 i i 在 DAG 上连接的所有点 j j ,枚举 j j 在原图上连接的所有点 k k ,如果 i i 小于 k k ,那么答案加上 c n t [ k ] cnt[k] ,然后令 c n t [ k ] cnt[k] 自增 1。
  3. i i 统计完后把 c n t cnt 清空。

可以证明,这一算法的时间复杂度为 O ( E 1.5 ) O(|E|^{1.5})

本题为例,示例代码如下:(这里边是按照 ( i , j ) (i, j) i > j i>j 连的,如果按照 i < j i<j 连接会 TLE)

int n, m;
int du[100005] = {0}, rk[100005];
int to[200005], nxt[200005], at[100005] = {0}, cnt = 0;
int to2[400005], nxt2[400005], at2[100005] = {0}, cnt2 = 0;
int pcnt[100005] = {0};
pair<int, int> pp[100005];
void init(){
    n = read(), m = read();
    for (int i = 1; i <= m; ++i){
        int u = read(), v = read();
        ++du[u], ++du[v];
        to2[++cnt2] = v, nxt2[cnt2] = at2[u], at2[u] = cnt2;
        to2[++cnt2] = u, nxt2[cnt2] = at2[v], at2[v] = cnt2;
    }
    for (int i = 1; i <= n; ++i)
        pp[i].first = -du[i], pp[i].second = -i;        // 降序
    sort(pp + 1, pp + n + 1);
    for (int i = 1; i <= n; ++i)
        rk[-pp[i].second] = i;
    for (int i = 1; i <= n; ++i)
        for (int j = at2[i]; j; j = nxt2[j])
            if (rk[i] < rk[to2[j]])
                to[++cnt] = to2[j], nxt[cnt] = at[i], at[i] = cnt;
}
void solve(){
    ll ans = 0;
    for (int i = 1; i <= n; ++i){
        int id = -pp[i].second;
        int v;
        for (int j = at[id]; j; j = nxt[j]){
            v = to[j];
            for (int k = at2[v]; k; k = nxt2[k])
                if (i < rk[to2[k]])
                    ans += pcnt[to2[k]]++;
        }
        for (int j = at[id]; j; j = nxt[j]){
            v = to[j];
            for (int k = at2[v]; k; k = nxt2[k])
                pcnt[to2[k]] = 0;
        }
    }
    printf("%lld\n", 8ll * ans);
}

有向图环计数

三元环和四元环和无向图类似,先按照无向图做,然后检查无向图中看到的环是不是在原有向图中有。

竞赛图三元环计数

可以用容斥。总共可能的三元环数目为 ( V 3 ) \binom{|V|}{3} ,如果三个点无法构成三元环,那么必然有一个点指向另外两个点。

对每一个点 i i ,设其出度为 o u t i out_i ,那么它作为这个指向两个点的点的方案数就是 ( o u t i 2 ) \binom{out_i}{2}

于是答案就是 ( V 3 ) ( o u t i 2 ) \binom{|V|}{3} - \sum \binom{out_i}{2}

竞赛图三元环期望

如果竞赛图中,有的边会随机指定方向,应该如何处理?

先对每一个点算出确定的入度 i n i in_i 和出度 o u t i out_i ,那么没确定的边数为 l i = V 1 i n i o u t i l_i =|V| - 1 - in_i - out_i 。这些边可以和已有的出边一同取消一个环,也可以两两取消环。

因此答案为 ( V 3 ) ( ( o u t i 2 ) + 1 2 o u t i l i + 1 4 ( l i 2 ) ) \binom{|V|}{3} - \sum \left(\binom{out_i}{2} + \frac{1}{2} out_i \cdot l_i + \frac{1}{4}\binom{l_i}{2}\right)

参考资料

猜你喜欢

转载自blog.csdn.net/zqy1018/article/details/108229296