三、四元環計數
阿新 • • 發佈:2021-01-23
-
三元環
題目連結-
題目:
給定一個 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) (1≤n≤105,1≤m≤2×105) -
題解:
使用根號分治的思想。
基於原圖的連邊,將度數較小的點向度數較大的點連邊,如果度數相同,結點編號小的向結點編號大的連邊,形成一個新的有向圖,顯然這個有向圖是一個 D A G DAG DAG(因為從一個結點出發到達的點要麼度數比它大,要麼結點編號比它大,所以不會回到自己)。那麼原圖的三元環在新的有向圖上一定可以由形如 < u , v > , < u , w > , < v , w > <u,v>,<u,w>,<v,w>
上述演算法的複雜度是 O ( m m ) O(m \sqrt m) -
複雜度: 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) (1≤n≤105,1≤m≤2×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;
}