1. 程式人生 > 其它 >三、四元環計數

三、四元環計數

技術標籤:記錄圖論

  • 三元環

    題目連結
    • 題目:
      給定一個 n n n個結點 m m m條邊的無向圖,問有多少個三元環。
      ( 1 ≤ n ≤ 1 0 5 , 1 ≤ m ≤ 2 × 1 0 5 ) (1 \le n \le 10^5,1 \le m \le 2 \times 10^5) (1n105,1m2×105)
    • 題解:
      使用根號分治的思想。
      基於原圖的連邊,將度數較小的點向度數較大的點連邊,如果度數相同,結點編號小的向結點編號大的連邊,形成一個新的有向圖,顯然這個有向圖是一個 D A G DAG DAG(因為從一個結點出發到達的點要麼度數比它大,要麼結點編號比它大,所以不會回到自己)。那麼原圖的三元環在新的有向圖上一定可以由形如 < u , v > , < u , w > , < v , w > <u,v>,<u,w>,<v,w>
      <u,v>,<u,w>,<v,w>
      這樣的三條邊構成。先列舉點 u u u,再列舉點 u u u的出邊 < u , v > <u,v> <u,v>,然後列舉點 v v v的出邊 < v , w > <v,w> <v,w>,判斷點 w w w是不是點 u u u的出點,如果是那麼就形成了一個三元環,可以在列舉點 u u u後先將點 u u u的所有出點打上標記,這樣就可以 O ( 1 ) O(1) O(1)判定點 w w w是不是點 u u u的出點了。
      上述演算法的複雜度是 O ( m m ) O(m \sqrt m)
      O(mm )
      的。首先列舉 u u u再列舉 u u u的出邊的過程相當於枚舉了新圖所有的邊,數量為 O ( m ) O(m) O(m)。對於點 v v v,如果 v v v在原圖的度數小於等於 m \sqrt m m ,那麼在新圖中的度數不會更多,所以在新圖中的度數為 O ( m ) O(\sqrt m) O(m );如果 v v v在原圖的度數大於 m \sqrt m m ,又由於它在新圖中只會向度數比它大的結點連邊,而這樣的結點數量為 O ( m ) O(\sqrt m) O(m ),所以點 v v v在新圖中的度數為 O ( m ) O(\sqrt m) O
      (m )
      。所以總的複雜度為 O ( m m ) O(m \sqrt m) O(mm )
    • 複雜度: O ( m m ) O(m \sqrt m) O(mm )
    • 程式碼:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<bitset>
#include<sstream>
#include<ctime>
//#include<chrono>
//#include<random>
//#include<unordered_map>
using namespace std;

#define ll long long
#define ls o<<1
#define rs o<<1|1
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
const double pi=acos(-1.0);
const double eps=1e-6;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const int maxn=1e5+5;
ll read(){
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int n,m;
vector<int>g[maxn],g2[maxn];
int deg[maxn],vis[maxn];
int main(void){
	//freopen("in.txt","r",stdin);
	scanf("%d%d",&n,&m);
	int u,v;
	for(int i=1;i<=m;i++){
		scanf("%d%d",&u,&v);
		g[u].pb(v);
		g[v].pb(u);
		deg[u]++;
		deg[v]++;
	}
	for(int u=1;u<=n;u++){
		for(auto v:g[u]){
			if((deg[u]<deg[v])||(deg[u]==deg[v]&&u<v)){
				g2[u].pb(v);
			}
		}
	}
	ll ans=0;
	for(int u=1;u<=n;u++){
		for(auto v:g2[u])vis[v]=u;
		for(auto v:g2[u]){
			for(auto w:g2[v]){
				if(vis[w]==u){
					++ans;
				}
			}
		}
	}
	printf("%lld\n",ans);
	return 0;
}
  • 四元環

    • 題目:
      給定一個 n n n個結點 m m m條邊的無向圖,問有多少個四元環。
      ( 1 ≤ n ≤ 1 0 5 , 1 ≤ m ≤ 2 × 1 0 5 ) (1 \le n \le 10^5,1 \le m \le 2 \times 10^5) (1n105,1m2×105)
    • 題解:
      按照三元環的方式建出有向圖,並按照這個規則給每個結點賦一個排名 r k rk rk,如果還想按三元環的思路往下走會遇到一個問題,就是原圖的四元環在新建的有向圖中可能有兩種形式,
      在這裡插入圖片描述
      所有需要使用別的列舉方法。可以發現這兩種形式都可以由兩條無向邊+兩條有向邊表示,即
      在這裡插入圖片描述
      且兩條無向邊的替代位置唯一,所以我們可以把原圖的四元環當成兩段無向邊 ( u , v ) (u,v) (u,v)+有向邊 < v , w > <v,w> <v,w>拼起來的,且無論是哪種情況, r k u < r k w rk_u<rk_w rku<rkw。先列舉 u u u,再列舉原圖 u u u的出邊 ( u , v ) (u,v) (u,v),然後列舉新圖 v v v的出邊 < v , w > <v,w> <v,w>,令 c n t w cnt_w cntw為從 u u u出發經過之前提到的有向+無向到達 w w w的路徑條數,那麼如果 r k w > r k u rk_w>rk_u rkw>rku,答案加上 c n t w cnt_w cntw,表示拼接上之前的路徑,並且令 c n t w cnt_w cntw加1,表示為之後的路徑提供貢獻,這樣可以避免重複計算。複雜度和之前三元環的分析是類似的。
    • 複雜度: O ( m m ) O(m \sqrt m) O(mm )
    • 程式碼:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<bitset>
#include<sstream>
#include<ctime>
//#include<chrono>
//#include<random>
//#include<unordered_map>
using namespace std;

#define ll long long
#define ls o<<1
#define rs o<<1|1
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
const double pi=acos(-1.0);
const double eps=1e-6;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const int maxn=1e5+5;
ll read(){
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int n,m;
vector<int>g[maxn],g2[maxn];
int cnt[maxn],deg[maxn],id[maxn],rk[maxn];
int cmp(int a,int b){
	return deg[a]<deg[b]||(deg[a]==deg[b]&&a<b);
}
int main(void){
	// freopen("in.txt","r",stdin);
	scanf("%d%d",&n,&m);
	int u,v;
	for(int i=1;i<=m;i++){
		scanf("%d%d",&u,&v);
		g[u].pb(v);
		g[v].pb(u);
		deg[u]++;
		deg[v]++;
	}
	for(int i=1;i<=n;i++)id[i]=i;
	sort(id+1,id+n+1,cmp);
	for(int i=1;i<=n;i++)rk[id[i]]=i;
	for(int u=1;u<=n;u++){
		for(auto v:g[u]){
			if(deg[u]<deg[v]||(deg[u]==deg[v]&&u<v)){
				g2[u].pb(v);
			}
		}
	}
	ll ans=0;
	for(int u=1;u<=n;u++){
		for(auto v:g[u]){
			for(auto w:g2[v]){
				if(rk[w]>rk[u])ans+=cnt[w]++;
			}
		}
		for(auto v:g[u]){
			for(auto w:g2[v]){
				if(rk[w]>rk[u])cnt[w]=0;
			}
		}
	}
	printf("%lld\n",ans);
	return 0;
}