1. 程式人生 > 實用技巧 >稀疏圖三元環計數

稀疏圖三元環計數

度數分治

將度數 \(\ge \sqrt m\) 的點稱為大點,度數 \(\lt \sqrt m\) 的點稱為小點。

列舉點 \(a\)

Case 1:\(a\) 是大點,標記與 \(a\) 相連的小點和與 \(a\) 相連的編號比 \(a\) 小的大點,列舉圖中的每一條邊,判斷兩端點是否都被標記。\(O(m \sqrt m)\)

Case 2:\(a\) 是小點,列舉與 \(a\) 相連的編號比 \(a\) 小的小點 \(b\),列舉與 \(b\) 相連的編號比 \(a\) 小的小點 \(c\),判斷 \(c\) 是否被標記,標記點 \(b\)\(O(m \sqrt m)\)

所有包含大點的三元環都只會在編號最大的大點處被統計,所有隻包含小點的三元環都只會在編號最大的點處被統計,不重不漏。

Case 1 的複雜度顯然。Case 2 中每條小點之間的邊 \((a, b)\) 都只會被算一次,貢獻 \(deg_b\) 的複雜度。

例題:HDU6184

#include<cstdio>
#include<cmath>
#include<cstring>
#include<map>
#include<algorithm>
using namespace std;

typedef long long ll;

const int N = 100005;
const int M = 200005;

int n, m, sm, hd[N], cnt, deg[N];
struct Edge {
	int to, nxt;
} e[M << 1];
struct Gedge {
	int u, v;
} ge[M];
map<pair<int,int>, int> tot;
bool tag[N];

void adde(int u, int v) {
	e[++cnt].to = v; e[cnt].nxt = hd[u]; hd[u] = cnt;
	e[++cnt].to = u; e[cnt].nxt = hd[v]; hd[v] = cnt;
}
#define mkpr(u, v) make_pair(min(u, v), max(u, v))
void work1(int u) {
	for(int i = hd[u]; i; i = e[i].nxt) {
		int v = e[i].to;
		if(deg[v] < sm || v < u) {
			tag[v] = 1;
		}
	}
	for(int i = 1; i <= m; ++i) {
		if(tag[ge[i].u] && tag[ge[i].v]) {
			++tot[mkpr(u, ge[i].u)];
			++tot[mkpr(u, ge[i].v)];
			++tot[mkpr(ge[i].u, ge[i].v)];
		}
	}
	for(int i = hd[u]; i; i = e[i].nxt) {
		tag[e[i].to] = 0;
	}
}
void work2(int u) {
	for(int i = hd[u]; i; i = e[i].nxt) {
		int v = e[i].to;
		if(deg[v] >= sm || v > u) continue;
		for(int j = hd[v]; j; j = e[j].nxt) {
			int w = e[j].to;
			if(tag[w]) {
				++tot[mkpr(u, v)];
				++tot[mkpr(u, w)];
				++tot[mkpr(v, w)];
			}
		}
		tag[v] = 1;
	}
	for(int i = hd[u]; i; i = e[i].nxt) {
		tag[e[i].to] = 0;
	}
}
int main() {
	while(~scanf("%d%d", &n, &m)) {
		cnt = 0;
		memset(hd, 0, sizeof(int) * (n + 1));
		memset(deg, 0, sizeof(int) * (n + 1));
		for(int i = 1; i <= m; ++i) {
			int u, v;
			scanf("%d%d", &u, &v);
			ge[i].u = u; ge[i].v = v;
			adde(u, v);
			++deg[u]; ++deg[v];
		}
		sm = (int)sqrt(m);
		tot.clear();
		for(int u = 1; u <= n; ++u) {
			if(deg[u] >= sm) work1(u);
			else work2(u);
		}
		ll ans = 0;
		for(int i = 1; i <= m; ++i) {
			int tmp = tot[mkpr(ge[i].u, ge[i].v)];
			ans += (ll)tmp * (tmp - 1) / 2;
		}
		printf("%lld\n", ans);
	}
	return 0;
}