P1989 无向图三元环计数

P1989 无向图三元环计数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

属于是只有大文学家能写出来,我只能抄在积累本上的那种。

考虑给每个点赋权 \(a_u\),权值两两不同,然后给原图定向:

对于原图上的一条边 \((u, v)\),让 \(a\) 小的连向 \(a\) 大的。

不难发现形成的有向图是 DAG,因为如果 \(u \to v \to w \to u\) 就有 \(a_u < a_v < a _w < a_u\),矛盾。

所以原来的三元环在新图中一定是这样一个形态:\((u \to v), (u \to w), (v \to w)\)。没有其他形态,不信你画画看。

我们只要枚举 \(u\) 的出点 \(v\),再枚举 \(v\) 的出点 \(w\),检查 \(w\) 是否是 \(u\) 的出点即可。

只需枚举 \(u\) 时,将 \(u\) 的所有出点打上标记,就可以 \(\mathcal{O}(1)\) 判断 \(w\) 是否为 \(u\) 的出点。

现在,我们将 \(u\) 的权值 \(a_u\) 赋为 \((d_u, u)\)\(d_u\) 表示 \(u\) 在原图上的度数。

这是一个有序数对,比较时优先比较第一维再比较第二维,容易看出,因为第二维的存在,权值有了互异性。

如此以来可以保证:新图中每个点的出度不大于 \(\sqrt m\)。简单证明一下。对于点 \(u\)

  • 如果 \(d_u \le \sqrt m\),显然新图上 \(u\) 的出度不会超过 \(d_u\),所以新出度不大于 \(\sqrt m\)
  • 如果 \(d_u > \sqrt m\),因为 \(\sum d = m\),因此 \(d\) 大于 \(\sqrt{m}\) 的点不会超过 \(\sqrt m\) 个,大于 \(d_u\) 的点则更不会超过 \(\sqrt m\) 个。根据我们的连边规则,新出度也不会大于 \(\sqrt m\)

枚举 \(u\) 和枚举其出点 \(v\) 的二重循环可看作枚举边,复杂度为 \(m\);而枚举 \(v\) 的出点复杂度为 \(\sqrt m\)。所以,该算法时间复杂度为 \(\Theta(m \sqrt m)\)

更帅的写法是 \(\Theta(m^{1.5})\)(????)。

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2023-01-04 13:51:33 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2023-01-04 13:56:20
 */
#include <bits/stdc++.h>
inline int read() {
    int x = 0;
    bool f = true;
    char ch = getchar();
    for (; !isdigit(ch); ch = getchar())
        if (ch == '-')
            f = false;
    for (; isdigit(ch); ch = getchar())
        x = (x << 1) + (x << 3) + ch - '0';
    return f ? x : (~(x - 1));
}

const int maxn = (int)1e5 + 5;
const int maxm = (int)2e5 + 5;

int d[maxn], us[maxm], vs[maxm];
std :: vector <int> G[maxn];

int t[maxn];

int main() {
    int n = read(), m = read();
    for (int i = 1; i <= m; ++i) {
        int u = read(), v = read();
        us[i] = u;
        vs[i] = v;
        ++d[u];
        ++d[v];
    }

    for (int i = 1; i <= m; ++i) {
        int u = us[i], v = vs[i];
        if (d[u] > d[v])
            std :: swap(u, v);
        else if (d[u] == d[v] && u > v)
            std :: swap(u, v);
        G[u].push_back(v);
    }

    int ans = 0;
    for (int u = 1; u <= n; ++u) {
        for (int v : G[u])
            t[v] = u;
        for (int v : G[u])
            for (int w : G[v])
                if (t[w] == u)   
                    ++ans;
    }
    
    printf("%d\n", ans);
    return 0;
}
posted @ 2023-01-04 13:59  dbxxx  阅读(38)  评论(1编辑  收藏  举报