无向图子图计数
三元环计数
考虑给每个点赋一个排列权值 \(p_i\),使得 \(deg_x < deg_y \iff p_x < p_y\)。
此时有一个重要的性质:对于点 \(x\),原图中 \(x\) 的邻边 \((x, y)\) 中仅有约 \(\sqrt{2m}\) 条使得 \(p_x < p_y\)。
可以发现钦定原图上的三元环中 \(p_i\) 最小的与次小的边,取其出边中满足以上性质的并,即可 \(O(m \sqrt m)\)。
四元环计数
钦定原图上的四元环中 \(p_i\) 最大的点对面的点 \(a\),枚举 \(a \to b \to c\) 使得 \(p_a < p_c, p_b < p_c\),两条 \(a \to b \to c, a \to d \to c\) 拼一起就对应了原图上的四元环,也是 \(O(m \sqrt m)\)。
独立集/团计数(依赖于 \(n\))
团计数等价于补图上的独立集计数。
搜索 \(f(S) = f(S / \{ v \}) + f(S / \{ v \} \cup N(v))\),\(|S| > n / 2\) 时只有 \(2 ^ {n / 2}\) 种分支,\(|S| \le n / 2\) 可以记搜,\(O(2 ^ {n / 2})\)。
独立集/团计数(依赖于 \(m\))qoj7514
同上赋一个排列权值 \(p_i\),枚举每个点 \(x\) 作为团中编号最小的点。
取出满足 \(p_x < p_y\) 的邻边 \((x, y)\),求所有 \(y\) 生成的子图的团个数。
复杂度当度数为 \(\sqrt{2m}\) 的点越多时越大,故有复杂度为 \(O(\sqrt m \times 2 ^ {\sqrt{2m} / 2})\)。
四元团 - 四元独立集计数 gym103470K
考虑容斥原理,由于四元团里两两连了六条边,记 \(P_{S, i}\) 表示四元组 \(S\) 存在第 \(i\) 条边。
则四元独立集的数量 \(\displaystyle \sum_{S} \prod_i (1 - [P_{S, i}]) = \sum_{T \subseteq 6} (-1) ^ {|T|} \sum_{S} \prod_{i \in T} [P_{S, i}]\)。
此处有集合论定义 \(6 := \{0, 1, 2, 3, 4, 5\}\)。
将右侧 \(T = 6\) 的项移到左侧,答案即为 \(\displaystyle \sum_{T \subsetneq 6} (-1) ^ {|T|} \sum_{S} \prod_{i \in T} [P_{S, i}]\),可以发现能在 \(O(m \sqrt m)\) 内求出。
四元团计数 qoj6354
同上赋一个排列权值 \(p_i\),枚举每个点 \(x\) 作为团中编号最小的点。
取出所有以 \(x\) 为编号最小的点的三元环,取出所有 \(x\) 对应的边,在这些边上使用 \(nm / w\) 的三元环计数。
分析复杂度,\(\displaystyle O\left( \sum \frac{deg_u num_u}{w} \right) = O(m ^ 2 / w)\)。
练习 qoj6379
solution
枚举所有可能通过点粘合缩成的图,高斯消元大力跑系数即可,\(O(m\sqrt{m})\)。
#include <bits/stdc++.h>
using namespace std;
template<typename word, typename dword, word Mod> struct ModInt {
const static word mod = Mod; word v; constexpr ModInt() : v(0) {}
template<typename T, enable_if_t<is_unsigned_v<T>, int> = 0> constexpr ModInt(T _v) : v(_v % mod) {}
template<typename T, enable_if_t<is_signed_v<T>, int> = 0> constexpr ModInt(T _v) { _v %= mod, v = _v < 0 ? _v + mod : _v; }
static constexpr ModInt raw(word v) { ModInt x; x.v = v; return x; }
constexpr ModInt operator + (const ModInt o) const { return raw(v + o.v >= mod ? v + o.v - mod : v + o.v); }
constexpr ModInt operator - (const ModInt o) const { return raw(v < o.v ? v + mod - o.v : v - o.v); }
constexpr ModInt operator - () const { return v ? raw(mod - v) : raw(0); }
constexpr ModInt operator * (const ModInt o) const { return raw((word)((dword)v * o.v % mod)); }
template<typename T, enable_if_t<is_integral_v<T>, int> = 0> constexpr friend ModInt operator * (const T x, const ModInt o) { return o * x; }
ModInt &operator += (const ModInt o) { return *this = *this + o; }
ModInt &operator -= (const ModInt o) { return *this = *this - o; }
ModInt &operator *= (const ModInt o) { return *this = *this * o; }
ModInt &operator ++ () { return *this = *this + 1; }
ModInt &operator -- () { return *this = *this - 1; }
ModInt &operator ++ (int) { const ModInt w = *this; ++*this; return w; }
ModInt &operator -- (int) { const ModInt w = *this; --*this; return w; }
friend istream &operator >> (istream &is, ModInt &x) { long long v; is >> v; x = ModInt(v); return is; }
friend ostream &operator << (ostream &os, const ModInt &x) { return os << x.v; }
};
template<uint32_t Mod> using ModInt32 = ModInt<uint32_t, uint64_t, Mod>;
using Int = ModInt32<998244353>;
int main() {
int n, m; cin >> n >> m; vector<vector<pair<int, int>>> G(n);
for(int x, y, i = 0; i < m; ++i)
cin >> x >> y, G[x].emplace_back(y, i), G[y].emplace_back(x, i);
vector<vector<pair<int, int>>> H(n);
const auto < = [&](int i, int j) -> bool
{ return make_pair(G[i].size(), i) < make_pair(G[j].size(), j); };
for(int i = 0; i < n; ++i)
for(const auto &[j, e] : G[i]) if(lt(i, j)) H[i].emplace_back(j, e);
vector<Int> C3_v(n), C3_e(m), C4_e(m), P3_v(n), P4_v(n); vector<int> tmp(n, -1);
for(int i = 0; i < n; ++i)
for(const auto &[j, e1]: H[i]) {
for(const auto &[k, e2] : H[j]) tmp[k] = e2;
for(const auto &[k, e3] : H[i]) if(~tmp[k])
++C3_v[i], ++C3_v[j], ++C3_v[k], ++C3_e[e1], ++C3_e[tmp[k]], ++C3_e[e3];
for(const auto &[k, e2] : H[j]) tmp[k] = -1;
}
for(int i = 0; i < n; ++i) {
for(const auto &[j, _] : G[i])
for(const auto &[k, _] : H[j]) if(lt(i, k)) ++tmp[k];
for(const auto &[j, e1] : G[i])
for(const auto &[k, e2] : H[j]) if(lt(i, k))
C4_e[e1] += tmp[k], C4_e[e2] += tmp[k];
for(const auto &[j, _] : G[i])
for(const auto &[k, _] : H[j]) if(lt(i, k)) --tmp[k];
}
for(int i = 0; i < n; ++i)
for(const auto &[j, _] : G[i]) P3_v[i] += G[j].size();
for(int i = 0; i < n; ++i)
for(const auto &[j, _] : G[i]) P4_v[i] += P3_v[j];
Int c1, c2, c3, c4, c5, c6, c7, c8, c9;
for(int i = 0; i < n; ++i)
c1 += C3_v[i] * P4_v[i], c2 += C3_v[i] * P3_v[i],
c3 += C3_v[i], c4 += C3_v[i] * G[i].size(),
c5 += C3_v[i] * G[i].size() * G[i].size(), c6 += C3_v[i] * C3_v[i];
for(int i = 0; i < n; ++i) for(const auto &[j, e] : H[i])
c7 += C3_e[e] * C3_e[e], c8 += C3_e[e] * C4_e[e],
c9 += C3_e[e] * C3_e[e] * (G[i].size() + G[j].size());
cout << c1 - 3 * c2 - 10 * c3 + 7 * c4 - c5 - 2 * c6 + 10 * c7 - 2 * c8 - c9;
}
练习 gym103469G
solution
同理使用容斥原理,通过枚举异色边,\(K_4\) 中含 \(6 + 6\) 个 Anton 图,而枚举排列除以二给出 \(K_4\) 中含 \(4!/2\) 个 Yahor 图,二者刚好抵消,故可以做到 \(O(n^3/w)\)。
进一步的,可以计算风筝图和 \(C_4\) 容斥系数为 \(0\),因此甚至不用计数四元环。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
int n, m = 0; cin >> n; vector<bitset<2000>> G(n);
for(int i = 0; i < n; ++i)
for(int j = 0; j < n; ++j) {
char c; cin >> c; G[i][j] = c == 'Y';
if(i < j) m += c == 'Y';
}
ll c1 = 1ll * m * (n - 2) * (n - 3) / 2;
ll c2 = 0, c3 = 0, c4 = 0, c5 = 0, c6 = 0, c7 = 0;
for(int i = 0; i < n; ++i) {
const ll w1 = G[i].count();
c2 += w1 * (w1 - 1) / 2 * (n - 3);
c3 += w1 * (w1 - 1) * (w1 - 2) / 6;
for(int j = i + 1; j < n; ++j) if(G[i][j]) {
const ll w2 = G[j].count();
const ll w12 = (G[i] & G[j]).count();
c4 += m - (w1 + w2 - 1), c5 += (w1 - 1) * (w2 - 1) - w12;
c6 += w12 * (n - 3), c7 += w12 * (w1 - 2 + w2 - 2);
}
}
printf("%lld", -c1 + 2 * c2 - 3 * c3 + c4 - 2 * c5 - c6 + c7);
}

浙公网安备 33010602011771号