$\newcommand{\O}{\mathrm{O}}$

単純無向グラフに含まれる $C_4$(四角形) の数を答えるアルゴリズムである.
各頂点をその次数が高々 $\sqrt{m}$ 程度であるもの(low degree), それより大きいもの(high degree) に分けて処理を行う.
こちらのアルゴリズムと考え方は同じであり, 重複に注意して数えあげることで
$\O \left( m^{1.5} \right)$ 時間で $C_4$ の数を計算することができる.
また $n$ 頂点 $m$ 辺の単純無向グラフに含まれる $C_4$ の数は $\O \left( m^{2} \right)$ 個であることにも注意する.
時間計算量: $\O(m^{1.5})$
class CountingC4 {
private:
int V, threshold;
vector<vector<int> > G;
vector<vector<array<int, 2> > > memo;
vector<int> flag1, flag2;
void process_high_degree(long long& ans){
for(int i = 0; i < V; ++i){
if((int)G[i].size() <= threshold) continue;
for(const int u : G[i]){
if(u > i) flag1[u] = 1;
flag2[u] = 1;
}
for(int j = 0; j < i; ++j){
if((int)G[j].size() > threshold) continue;
long long cnt1 = 0, cnt2 = 0;
for(const int u : G[j]){
if(u < j || !flag2[u]) continue;
if((int)G[u].size() > threshold) ++cnt1;
else ++cnt2;
}
ans += (cnt1 + cnt2) * (cnt1 + cnt2 - 1) / 2;
}
for(int j = i + 1; j < V; ++j){
long long cnt = 0;
for(const int u : G[j]){
if(flag1[u]) ++cnt;
}
ans += cnt * (cnt - 1) / 2;
}
for(const int u : G[i]) flag1[u] = flag2[u] = 0;
}
}
void process_low_degree(long long& ans){
for(int i = 0; i < V; ++i){
if((int)G[i].size() > threshold) continue;
for(const int u : G[i]){
for(const int v : G[i]){
if(v > u) memo[u].push_back({i, v});
}
}
}
for(int i = 0; i < V; ++i){
for(const auto& e : memo[i]){
if(e[0] < i) ++flag1[e[1]];
else ++flag2[e[1]];
}
for(const auto& e : memo[i]){
ans += (long long)(flag1[e[1]] + 2 * flag2[e[1]] - 1) * flag1[e[1]] / 2;
flag1[e[1]] = flag2[e[1]] = 0;
}
}
}
public:
CountingC4(const int node_size)
: V(node_size), threshold(0), G(V), memo(V), flag1(V, 0), flag2(V, 0){}
void add_edge(const int u, const int v){
G[u].push_back(v), G[v].push_back(u);
++threshold;
}
long long solve(){
threshold = floor(sqrt(2 * threshold)) / 2;
long long ans = 0;
process_high_degree(ans);
process_low_degree(ans);
return ans;
}
};
verify していません(verify 問題を知らない)
手元でかなりテストはした.