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;
}