三元环计数
算法如其名,就是用來找三元環的。
介紹
給出一張無向圖,問圖中有多少個三元組{x,y,z}\{x,y,z\}{x,y,z},滿足圖中存在 {x?y,y?z,z?x}\{x-y,y-z,z-x\}{x?y,y?z,z?x} 三條邊。
轉化
考慮將這張圖轉化為有向圖:對于一條無向邊 x?yx-yx?y,不妨設點 xxx 的度大于點 yyy 的度,那么就將這條無向邊變成 x→yx \to yx→y。假如兩點的度相同,就從編號大的往編號小的連邊。
這樣轉化后,這張圖一定是有向無環圖。
證明:
使用反證法,假設有一個環:a→b→c→aa\to b \to c \to aa→b→c→a,那么有(設 xxx 的度為 dxd_xdx?):da≥db≥dc≥dad_a \geq d_b \geq d_c \geq d_ada?≥db?≥dc?≥da?,要使該不等式成立,當且僅當滿足 da=db=dc=dad_a=d_b=d_c=d_ada?=db?=dc?=da?。
設 xxx 的編號為 idxid_xidx?,那么有:ida>idb>idc>idaid_a>id_b>id_c>id_aida?>idb?>idc?>ida?,即 ida>idaid_a>id_aida?>ida?,該柿子不成立,故假設不成立,證畢。
求解
步驟如下:
正確性
轉化完后,每一個三元環會變成下面這樣的東西:
只有枚舉到紅點的時候,這個三元環才會被計算到,這就保證了不會重復計算并且不會漏算。
時間復雜度
考慮每一條邊被遍歷的次數:對于一條邊 x→yx\to yx→y,他被遍歷的次數為 inxin_xinx?。(inxin_xinx?表示 xxx 的入度),那么總的時間復雜度就是每一條邊的 inxin_xinx? 之和。
又可以發現,inxin_xinx? 的上限就是 m\sqrt mm?,因為要求每個連向 xxx 的點的度都大于 inxin_xinx?,也就是說,有 inxin_xinx? 個點的度大于 inxin_xinx?,這樣就至少需要 inx2in_x^2inx2? 條邊,所以 inx2≤m?inx≤min_x^2\leq m \Rightarrow in_x\leq \sqrt minx2?≤m?inx?≤m?。
例題
題目傳送門
顯然,題目中要求的四元組,也就是兩個有一條公共邊的三元環拼在一起,那么求出每條邊在多少個三元環中(設 totitot_itoti? 表示第 iii 條邊在多少個三元環里),然后 ∑i=1mCtoti2\sum_{i=1}^m C_{tot_i} ^2∑i=1m?Ctoti?2? 就是答案。
代碼如下:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define maxn 300010 #define ll long longint n,m; struct edge{int y,next;}; edge e[maxn*4]; int len; int first[maxn]; void buildroad(int x,int y) {e[++len]=(edge){y,first[x]};first[x]=len; } struct node{int x,y;}; node edges[maxn*2]; int du[maxn],id[maxn],to[maxn],tot[maxn]; inline ll C(int x){return (ll)x*(x-1)/2ll;}int main() {while(~scanf("%d %d",&n,&m)){memset(du,0,sizeof(du));//記錄每個點的度的數組for(int i=1;i<=m;i++)scanf("%d %d",&edges[i].x,&edges[i].y),du[edges[i].x]++,du[edges[i].y]++;memset(first,0,sizeof(first));len=0;for(int i=1,x,y;i<=m;i++){x=edges[i].x,y=edges[i].y;if(x>y)swap(x,y);//讓x成為編號小的點if(du[x]>=du[y])buildroad(x,y);//度大的往小的連有向邊else buildroad(y,x);}memset(tot,0,sizeof(tot));//別忘了初始化各種數組memset(to,0,sizeof(to));//to[i]表示當前點到點i的邊是第幾條邊memset(id,0,sizeof(id));//id表示每個點的標記for(int i=1;i<=n;i++){int x=i;for(int j=first[x];j;j=e[j].next)//枚舉能到達的點,給他們大商標機id[e[j].y]=x,to[e[j].y]=j;//打標記,記錄tofor(int j=first[x];j;j=e[j].next)//再次遍歷所有有標記的點{for(int k=first[e[j].y];k;k=e[k].next)//遍歷有標記的點能到達的點if(id[e[k].y]==x)tot[j]++,tot[k]++,tot[to[e[k].y]]++;//假如能到達一個有標記的點,那么就找到了一個三元環,給這三條邊都打上標記}}ll ans=0;for(int i=1;i<=len;i++)//統計答案ans+=C(tot[i]);printf("%lld\n",ans);} }總結
- 上一篇: 软件项目管理第4版课后习题[附解析]第二
- 下一篇: 如何在CSDN编辑器(Markdown编