【理论】三元环计数
大概算是自创的一种方法?不知道和标准做法是否本质相同。
以下视作 \(n=m\)。
考虑根据编号大小定向,即若对于一条无向边 \((u,v)\) 将其转化为 \(\min(u,v)\to\max(u,v)\) 的一条有向边,这样的好处是其有了严格的拓扑序,这样能够保证统计的唯一性。
紧接着考虑对于出度大小分治:
-
若一个点 \(x\) 的出度 \(d>\sqrt n\),采取 \(O(n)\) 暴力,先将所有存在 \(x\to u\) 的点 \(u\) 标记,接着将所有存在 \(x\to u\) 的点 \(u\) 的所有出边扫一遍,查看点 \(u\) 的出边有多少个点已标记,将其计入总答案,不难发现 \(d>\sqrt n\) 的点的个数是 \(O(\sqrt n)\) 级别,总复杂度为 \(O(n)\times O(\sqrt n)=O(n\sqrt n)\)。
-
若一个点 \(x\) 的出度 \(d\leq\sqrt n\),采取 \(O(d^2)\) 暴力,枚举 \(u,v\) 使得同时存在 \(x\to u,x\to v,u\to v\) 三条边,如何快速判断是否存在边 \(u\to v\)?考虑将每个点的所有出边按终点编号大小排序。枚举 \(u,v\) 时,每次固定 \(u\),枚举 \(v\),由于 \(v\) 单调,而以 \(u\) 为起点的所有边的终点也单调(因为排序),所以直接 2-pointer 即可。时间复杂度分析的话,显然 \((a+b)^2>a^2+b^2(a,b>0)\),所以将总度数分成若干个 \(d_{max}=\sqrt n\) 最劣,此时总复杂度为 \(d_{max}^2\times\frac{n}{d_{max}}=O(n\sqrt n)\)。
由此,总复杂度为 \(O(n\sqrt n)\) 足以通过本题。
代码
#include<bits/stdc++.h>
using namespace std;
#define inf 0x7fffffff
#define timeused() (double)clock()/CLOCKS_PER_SEC
#define rep(i,a,b) for(register int i=a,i##end=b;i<=i##end;++i)
#define repp(i,a,b) for(register int i=a,i##end=b;i>=i##end;--i)
#define mp make_pair
#define pb push_back
typedef long long ll;
typedef unsigned long long ull;
template<typename T> inline T rd(T& x){
T f=1;x=0;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(T)(c-'0');
x*=f;
return x;
}
ll n,m,u,v,ans;
bool pd[100005];
vector<ll> e[100005];
int main(){
rd(n);
rd(m);
rep(i,1,m){
rd(u);
rd(v);
if(u>v) swap(u,v);
e[u].pb(v);
}
rep(i,1,n) sort(e[i].begin(),e[i].end());
rep(i,1,n){
if(e[i].size()>=sqrt(n)){
rep(j,0,e[i].size()-1) pd[e[i][j]]=1;
rep(j,0,e[i].size()-1) rep(k,0,e[e[i][j]].size()-1) ans+=pd[e[e[i][j]][k]];
rep(j,1,n) pd[j]=0;
}
else{
rep(j,0,e[i].size()-1){
if(e[e[i][j]].empty()) continue;
ll now=0;
rep(k,0,e[i].size()-1){
while(e[e[i][j]][now]<e[i][k]&&now+1<e[e[i][j]].size()) ++now;
if(e[e[i][j]][now]==e[i][k]) ++ans;
}
}
}
}
printf("%lld",ans);
}