图论小记

rlc202204 / 2024-01-24 / 原文

三元环计数

无向图三元环计数指在 \(G=(V,E)\) 中有多少个无序点对 \((x,y,z)\) 满足 \((x,y),(y,z),(x,z) \in E\).

首先,我们将无向图转化成有向图,设 \(d_i\) 表示 \(i\) 的度数,我们按照 \(d_i\) 从小到大排序(相同则按编号从小到大),然后每条边由排在前的指向排在后的。

不难发现,原图中的三元环在新图中必然是 \(x\) 指向 \(y\)\(z\)\(y\) 指向 \(z\) 的形式。

所以我们枚举 \((x, y)\) 这条边,然后枚举所有 \(y\) 的出边指向节点 \(z\),判断 \(z\) 是不是 \(x\) 指向的节点。具体实现可以向将所有 \(x\) 指向节点打标记,这样方便判断。

这个做法看似暴力,但是其时间复杂度是 \(O(m\sqrt{m})\) 的,因为我们可以证明新图中每个点的出度不超过 \(2\sqrt{m}\)

证明不难,反证法,如果 \(d_x > 2\sqrt{m}\),则所有 \((x,y)\)\(y\) 也会 \(d_y > d_x > 2\sqrt{m}\),这样总边数就超过了 \(m\),于是证出这个结论。

这其实也间接证明了三元环的个数不超过 \(m\sqrt{m}\),所以我们实际可以找出所有的三元环。这在解决一些问题时很有帮助。

例1: P1989 无向图三元环计数

思路:

模板题。

点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5;
const int M = 2e5 + 5;

int n, m;
vector<int> e[N];
int d[N] = {0};

typedef pair<int, int> pii;
pii ed[M];

bool cmp(int x, int y) {//x是否在 y 前面 
	if (d[x] != d[y])
		return d[x] < d[y];
	return x < y;	
}

bool f[N] = {false};

int main() {
	cin >> n >> m;
	for (int i = 1, u, v; i <= m; i++) {
		cin >> u >> v;
		d[u]++, d[v]++;
		ed[i] = make_pair(u, v);
	}
	for (int i = 1; i <= m; i++) 
		if (cmp(ed[i].first, ed[i].second))
			e[ed[i].first].push_back(ed[i].second);
		else
			e[ed[i].second].push_back(ed[i].first);
	int ans = 0;
	for (int x = 1; x <= n; x++) {
		for (auto y: e[x])
			f[y] = true;
		for (auto y: e[x])
			for (auto z: e[y])
				if (f[z])
					ans++;
		for (auto y: e[x])
			f[y] = false;	
	}
	cout << ans << endl;
	return 0;
} 

例2: CF985G Team Players

思路:

不错的计数题。

显然原图的补图边数很多,肯定不能真的建出来找三元环。

考虑先算出所有点对的贡献,再减去不符合要求的点对的贡献。

按照三元环处理套路,还是建出新图,则不符合要求的有三种情况:三元环,一条链,一条边加上一个点。

分别去计数这几类的数量即可,注意一条链在计算时会将三元环算三次,二一条边加一个点会将一条链算两次,三元环算三次,最后要减去这些。

很多细节,代码有点长,不太好写。

例3: 有向图三元环计数问题

思路:

其实很简单,转化成无向图,显然所有有向图的三元环在无向图中还是三元环,找出无向图的三元环再判断即可。

例4: HDU6184 Counting Stars

思路:

不难。求出所有三元环,设 \(c_i\) 表示第 \(i\) 条边被几个三元环包含,答案就是 \(\sum_i \binom{c_i}{2}\)

例5: #6076. 「2017 山东一轮集训 Day6」三元组

思路:

前置知识:莫比乌斯反演。

看到这种 \(\gcd(i,j)=1\) 的形式就要想到莫反, 我们要计算的就是:

\[\sum_{i=1}^a\sum_{j=1}^b\sum_{k=1}^c[\gcd(i,j)=1][\gcd(i,k)=1][\gcd(j,k)=1] \]

运用莫比乌斯函数和莫反套路,我们可以转化成:

\[\sum_{i=1}^a\sum_{j=1}^b\sum_{k=1}^c\mu(i)\mu(j)\mu(k)\lfloor\frac{a}{\operatorname{lcm}(i,j)}\rfloor\lfloor\frac{b}{\operatorname{lcm}(i,k)}\rfloor\lfloor\frac{c}{\operatorname{lcm}(j,k)}\rfloor \]

观察这个式子,这题数据范围 \(a,b,c \le 50000\)

首先,有很多 \(\mu\) 取值都是 \(0\),我们可以去掉它们。

其次,我们发现只有 \(\operatorname{lcm}(i,j) \le 50000\) 时才有贡献,考虑到这是三个数的问题,我们不妨将所有 \(\operatorname{lcm}(i,j) \le 50000\) 的连一条 \((i,j)\)

现在我们就是要找出所有的三元环,然后计算贡献即可。

实测最后建出来的边其实不多,大概是 \(3\times 10^5\),瓶颈在于建图。

我们没必要枚举 \(i,j\),反过来想,枚举 \(\operatorname{lcm}(i,j)\) 然后枚举其所有因子,在判断是否可行即可。