1. 程式人生 > 其它 >P1989 無向圖三元環計數

P1989 無向圖三元環計數

原題連結

簡要題意:

給定 \(n\) 個點 \(m\) 條邊的無向圖,求其三元環個數。

\(n \leq 10^5 , m \leq 2 * 10^5\).

首先考慮一個暴力做法。

其實我們就是要尋找有多少組 \((u,v,w)\) 能使同時存在三條邊 \(u \leftrightarrow v , u \leftrightarrow w , v \leftrightarrow w\).

於是我們可以暴力列舉一條邊 \(u \leftrightarrow v\),並找它們連邊的公共點 \(w\) 的個數。

複雜度顯然,\(\mathcal{O}(m^2)\).

對於重複問題,我們可以考慮,只列舉 \(u < v\)

的邊,然後將答案 \(\div 3\).

但實際上,對於暴力做法,我們應該考慮,如何減少需要計算的邊的數量。

不妨從對重複問題的處理入手,是否可以只考慮 \(\text{deg}(u) < \text{deg}(v)\) 的邊?(其中 \(\text{deg}(x)\) 表示 \(x\) 的度)

顯然,只要我們能明確任意一個全序,那麼都不會影響答案的正確性。於是對於 \(u\)\(v\),只考慮:

\[\text{deg}(u) < \text{deg}(v) \space 或者 \space \text{deg}(u) = \text{deg}(v) \& u < v \]

的邊。建新圖。

這樣能否減少合法邊的數量呢?答案是肯定的。

對於度 \(\leq \sqrt{m}\) 的點,那麼我們直接在新圖上暴力,單點複雜度不超過 \(\sqrt{m}\). 總共不超過 \(m \sqrt{m}\).

對於度 \(> \sqrt{m}\) 的點,那麼這樣的點最多不超過 \(\sqrt{m}\) 個,同樣是新圖上暴力,單點複雜度不超過 \(m\),總共仍然是 \(m \sqrt{m}\).

時間複雜度:\(\mathcal{O}(m \sqrt{m})\).

注意到,暴力的時候請對 \(u\) 的點打標記,而不是每次暴力掃一邊,或者每一個用 find。實測洛谷暴力或用 find 無法通過,而打標記的小優化可以通過。

#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 1;

inline int read() {
	char c=getchar(); int f=1,x=0;
	while(!isdigit(c)) {if(c=='-') f=-f; c=getchar();}
	while(isdigit(c)) {x=x*10+c-'0'; c=getchar();}
	return x*f;
}

struct data {
	int u,v;
} e[N];

int n,m,deg[N],ans=0;
vector <int> G[N];
bool vis[N];

int main() {
	n=read(),m=read();
	for(int i=1;i<=m;i++) {
		int u=read(),v=read();
		deg[u]++; deg[v]++;
		e[i].u=u; e[i].v=v;
	}
	for(int i=1;i<=m;i++)
		if(deg[e[i].u] < deg[e[i].v] || (deg[e[i].u] == deg[e[i].v] && e[i].u < e[i].v)) G[e[i].u].push_back(e[i].v);
		else G[e[i].v].push_back(e[i].u) , swap(e[i].v,e[i].u);
	for(int i=1;i<=m;i++) {
		int u = e[i].u , v = e[i].v;
		for(int i=0;i<G[u].size();i++) vis[G[u][i]] = 1;
		for(int j=0;j<G[v].size();j++) ans += vis[G[v][j]];
		for(int i=0;i<G[u].size();i++) vis[G[u][i]] = 0;
	}	
	printf("%d\n",ans);
	return 0;
}
簡易的程式碼勝過複雜的說教。