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\) 的出点。

现在,我们将 \(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)\)

/*
 * @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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值