前言
本文仅为个人记录,参考价值不大,且仅写了几个例题,以后遇到这方面的题会再补充。
三元环计数
实现
首先显然有 \(O(nm)\) 的暴力,考虑优化这个过程。
将点按度数从小到大排序,若度数相等则按编号大小排序,然后将原来的无向边变为从前往后的有向边,在原图中的三元环 \((u,v,w)\) 变为在新图中的三条有向边\((u,v),(v,w),(u,w)\),考虑枚举 \(u\) 及其出边 \((u,v)\),并枚举 \(v\) 的出边 \((v,w)\),判断是否存在边 \((u,w)\) 即可。
直接看上去时间复杂度似乎是 \(O(m^2)\) 的,但是实际上复杂度味 \(O(m\sqrt m)\),以下为分析:
- 若 \(v\) 出度 \(\le \sqrt m\),时间复杂度为 \(O(\sqrt m)\)
- 若 \(v\) 出度 \(\ge \sqrt m\),因为序列按出度从小到大排序,之后的点度数必然 \(\ge \sqrt m\),而这样的点仅有 \(\sqrt m\) 个,所以时间复杂度为 \(O(\sqrt m)\)
综上,时间复杂度为 \(O(m\sqrt m)\)。
代码
void Main(){
n=rd,m=rd;
for(int i=1;i<=m;i++)
deg[A[i]=rd]++,deg[B[i]=rd]++;
for(int i=1;i<=m;i++){
int u=A[i],v=B[i];
if(deg[u]>deg[v]) swap(u,v);
else if((deg[u]==deg[v])&&u>v) swap(u,v);
G[u].pb(v);
}
long long ans=0;
for(int u=1;u<=n;u++){
for(int v:G[u]) vis[v]=u;
for(int v:G[u]) for(int w:G[v])
if(vis[w]==u) ans++;
}
cout<<ans<<endl;
}
例题
I.CodeForces - 985G
既然三个点不相邻十分难求,我们不如换个角度,求出所有三个点至少有一条边相邻的和即可,但是直接统计会重复,可以通过容斥解决。
以下为容斥的具体分析:
我们考虑求至少有一条边相邻的和会重复统计什么,我们发下如果有一个三元组至少有两条边那么就会被统计多次,需要减掉,但是又会多减去一部分,即三元环的和,加上即可。
对于求三元组至少有一条边的和,可以枚举边 \((u,v)\),然后对 \(w\) 的位置分讨即可。
对于求三元组至少有两条边的和,可以枚举中间点 \(v\),则 \(u,w\),都为 \(v\) 的邻点,将边按照大小排序,枚举节点 \(u\) 统计答案即可。
对于求三元环,你才为什么这道题是例题,直接按照三元环计数的方法统计即可,(其实这道题和三元环计数关系不大,但是比较妙就写进来了。
Code