无向图子图计数

三元环计数

考虑给每个点赋一个排列权值 \(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 &lt = [&](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);
}
posted @ 2024-01-31 19:48  JerryTcl  阅读(257)  评论(4)    收藏  举报