我是靠谱客的博主 谦让心锁,最近开发中收集的这篇文章主要介绍[AtCoder arc140 D] One to One (图论 计数 分治NTT),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

题意

初始有 n n n 个点,给定一个长度为 n n n 的数组 a i a_i ai,若 a i ≠ − 1 a_i ne -1 ai=1,则有无向边 ( i , a i ) (i, a_i) (i,ai),若 a i = − 1 a_i = -1 ai=1,则点 i i i 可以连向 1 ∼ n 1 sim n 1n 任意点,求所有图的联通块个数之和

1 ≤ n ≤ 2 × 1 0 3 , a i ∈ [ 1 , n ] ∪ { − 1 } 1 le n le 2 times 10 ^ 3, a_i in [1, n] cup {-1} 1n2×103,ai[1,n]{1}

998244353 998244353 998244353 取模。

分析:

首先考虑忽略 a i = − 1 a_i = -1 ai=1 的所有边,那么图中会有若干个连通块,这些连通块分为三种情况:

  • 基环树

对于环和基环树来说,因为是 n n n 个点和 n n n 条边,所以他们不可能有一条出边,换句话说,里边的点不可能包含 a i = − 1 a_i = -1 ai=1,而对于树来说,因为是 n n n 个点 n − 1 n - 1 n1 条边,所以有且仅有一条出边,也就是树里面只有一个 a i = − 1 a_i = -1 ai=1

这就代表树可以和其他连通块组成一个新的连通块,但是无论树如何连边,环和基环树的连通性都不会发生变化,也就是他始终有一个环,所以可以先计算出这部分的贡献,设图中环和基环树的数量为 u u u,树的数量为 v v v,则这部分贡献就为 u × n v u times n ^ {v} u×nv

接下来考虑树的所有连边情况,我们枚举 k k k 条边组成一个环,设第 i i i 棵树的大小为 f i f_i fi,每棵树则有生成函数 1 + f i x 1 + f_ix 1+fix,记 F ( x ) F(x) F(x) 为选若干个树构成一个环的方案数,可以用分治 NTT text{NTT} NTT 快速求出。
F ( x ) = ∏ i = 1 v ( 1 + f i x ) F(x) = prod_{i = 1} ^ {v} (1 + f_ix) F(x)=i=1v(1+fix)
每个点构成一个 k k k 元环是有顺序的,第一个点可以有 k − 1 k - 1 k1 种选择,第二个点有 k − 2 k - 2 k2 种选择,所以总共构成一个 k k k 元环的方案数为 ( k − 1 ) ! (k - 1)! (k1)!,还要考虑剩下没有被选出来的点,那么可以随便连,都不影响这个环,方案数就为 n v − k n ^ {v - k} nvk,那么答案就是
u × n v + ∑ k = 1 v ( k − 1 ) ! × [ x k ] F ( x ) × n v − k u times n ^ {v} + sum_{k = 1} ^ {v} (k - 1)! times [x ^ k]F(x) times n ^ {v - k} u×nv+k=1v(k1)!×[xk]F(x)×nvk
时间复杂度 O ( n log ⁡ 2 n ) O(nlog ^ 2n) O(nlog2n)

代码:

#include <bits/stdc++.h>
#define int long long
#define poly vector<int>
#define len(x) ((int)x.size())
using namespace std;
const int N = 3e3 + 5, G = 3, Ginv = 332748118, mod = 998244353;
struct DSU {
    vector<int> p, Size;
    DSU(int n) : p(n), Size(n, 1) {
        iota(p.begin(), p.end(), 0);
    }
    int find(int x) {
        return p[x] == x ? p[x] : p[x] = find(p[x]);
    }
    bool same(int u, int v) {
        return find(u) == find(v);
    }
    void merge(int u, int v) {
        u = find(u), v = find(v);
        if (u != v) {
            Size[v] += Size[u];
            p[u] = v;
        }
    }
};
int lim;
vector<int> rev(N);
int qmi(int a, int b) {
    int res = 1;
    while (b) {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}
void polyinit(int n) {
    for (lim = 1; lim < n; lim <<= 1);
    for (int i = 0; i < lim; i ++) rev[i] = (rev[i >> 1] >> 1) | (i & 1 ? lim >> 1 : 0);
}
void NTT(poly &f, int op) {
    for (int i = 0; i < lim; i ++) {
        if (i < rev[i]) swap(f[i], f[rev[i]]);
    }
    for (int mid = 1; mid < lim; mid <<= 1) {
        int Gn = qmi(op == 1 ? G : Ginv, (mod - 1) / (mid << 1));
        for (int i = 0; i < lim; i += mid * 2) {
            for (int j = 0, G0 = 1; j < mid; j ++, G0 = G0 * Gn % mod) {
                int x = f[i + j], y = G0 * f[i + j + mid] % mod;
                f[i + j] = (x + y) % mod, f[i + j + mid] = (x - y + mod) % mod;
            }
        }
    }
    if (op == -1) {
        int inv = qmi(lim, mod - 2);
        for (int i = 0; i < lim; i ++) f[i] = f[i] * inv % mod;
    }
}
poly operator * (poly f, poly g) {
    int n = len(f) + len(g) - 1;
    polyinit(n), f.resize(lim), g.resize(lim);
    NTT(f, 1), NTT(g, 1);
    for (int i = 0; i < lim; i ++) f[i] = f[i] * g[i] % mod;
    NTT(f, -1), f.resize(n);
    return f;
}
signed main() {
    cin.tie(0) -> sync_with_stdio(0);
    int n;
    cin >> n;
    DSU p(n + 1);
    vector<int> st(n + 1), fact(n + 1);
    fact[0] = 1;
    for (int i = 1; i <= n; i ++) {
        fact[i] = fact[i - 1] * i % mod;
    }
    for (int i = 1; i <= n; i ++) {
        int x;
        cin >> x;
        if (x != -1) {
            int flag = 0;
            if (p.same(i, x)) {
                flag = 1;
            }
            p.merge(i, x);
            if (flag) {
                st[p.find(i)] = 1;
            }
        }
    }
    int v = 0, u = 0;
    vector<poly> f(n + 1);
    for (int i = 1; i <= n; i ++) {
        if (p.find(i) == i) {
            if (!st[i]) {
                v ++;
                f[v].resize(2);
                f[v][0] = 1, f[v][1] = p.Size[i];
            } else {
                u ++;
            }
        }
    }
    int res = u * qmi(n, v) % mod;
    if (v) {
        function<poly(int, int)> dc = [&](int l, int r) {
            if (l == r) {
                return f[l];
            }
            int mid = l + r >> 1;
            return dc(l, mid) * dc(mid + 1, r);
        };
        poly ans = dc(1, v);
        for (int k = 1; k <= v; k ++) {
            res = (res + fact[k - 1] * ans[k] % mod * qmi(n, v - k) % mod) % mod;
        }
    }
    cout << res << endl;
}

最后

以上就是谦让心锁为你收集整理的[AtCoder arc140 D] One to One (图论 计数 分治NTT)的全部内容,希望文章能够帮你解决[AtCoder arc140 D] One to One (图论 计数 分治NTT)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(96)

评论列表共有 0 条评论

立即
投稿
返回
顶部