Loading

[每日 D] Multiplicative Arrays

前言

不求晓得这什么难度, 姑且假定是 \(\textrm{div 2 D}\)

观前提示: 这是假解, 仅供参考

思路

初步观察

你发现我们可以看做求这样一组 \(x_i\) 表示一个数字出现的次数, 使得

\[\begin{align} & \prod_{i = 1}^{k} i^{x_i} = C \\ & \sum_{i = 1}^{k} x_i = n \\ \end{align} \]

然后这样一组 \(x_i\) 对答案的贡献是一个多重集排列

\[\frac{n!}{\prod_{i = 1}^{k} x_i!} \]

显然你可以对 \(x_i\) \(\rm{dp}\) , 计算答案即可

具体一点
\(dp_{i, j, p}\) 表示考虑到数 \(i\) , 前面的乘积为 \(j\) , 和为 \(p\) 的方案数

这样子开都开不下无法转移, 但是你有一个观察

  • \(n\) 比较大的时候, 一定会有很大一部分都是 \(1\) , \(p\) 有用的部分只有 \(\log k\) 大小

\(\rm{dp}\) 计算分组

所以考虑转移, 这个时候我们从 \(i = 2\) 开始考虑

也就是说, 我们把问题修改成:
求一组 \(x_i, i \in [2, k]\)

\[\begin{align*} & \prod_{i = 2}^{k} i^{x_i} = C \\ & x_1 = n - \sum_{i = 2}^{k} x_i \\ \end{align*} \]

贡献还是

\[\begin{align*} &\frac{n!}{\prod_{i = 1}^{k} x_i!} \\ = & \frac{(x_1 + 1) \times (x_1 + 2) \times (x_1 + 3) \times \cdots n}{\prod_{i = 2}^{k} x_i!} \end{align*}\]

转移, 初始化 \(dp_{1, 1, 0} = 1\)

\[dp_{i, i^{x_i}j, p + x_i} \gets \sum_{x_i}^{i^{x_i}j \leq C, k + x_i \leq \log n} \frac{dp_{i - 1, j, p}}{x_i!} \]

容易发现枚举 \(x_i\)\(\log k\) 的, 枚举 \(p\)\(\log k\)
枚举 \(j\) 比较特殊, 对于一个确定的 \(x_i\) , 复杂度是 \(\frac{k}{i^{x_i}}\)
总复杂度只能证明是 \(k \log k \sim k^2\) 的, 不过感性理解是不大的


推的很乱, 整理一下

目标是找到一组分配 \(x\) , 使其满足

\[\begin{align*} & \prod_{i = 2}^{k} i^{x_i} = C \\ & x_1 = n - \sum_{i = 2}^{k} x_i \\ \end{align*} \]

然后计算方法
转移, 初始化 \(dp_{1, 1, 0} = 1\)

\[dp_{i, i^{x_i}j, p + x_i} \gets \sum_{x_i}^{i^{x_i}j \leq C, k + x_i \leq \log n} \frac{dp_{i - 1, j, p}}{x_i!} \]

计算答案

考虑答案计算

\[\begin{align*} ans_{x} \gets & \sum_{L = 1}^{n} \sum_{i = 1}^{\min (\log k, L)} dp_{k, x, i} \times [(L - i + 1) \times (L - i + 2) \times (L - i + 3) \times \cdots \times L] \\ = & \sum_{i = 1}^{\log k} dp_{k, x, i} \times \left\{\sum_{L = i}^{n} [(L - i + 1) \times (L - i + 2) \times (L - i + 3) \times \cdots \times L]\right\} \\ = & \sum_{i = 1}^{\log k} dp_{k, x, i} \times \left\{\sum_{L = i}^{n} \frac{L!}{(L - i)!}\right\} \\ = & \sum_{i = 1}^{\log k} dp_{k, x, i} \times \left\{\sum_{L = i}^{n} \frac{L!}{(L - i)!i!}\right\} \times i! \\ = & \sum_{i = 1}^{\log k} dp_{k, x, i} \times \left\{\sum_{L = i}^{n} {L \choose i}\right\} \times i! \\ = & \sum_{i = 1}^{\log k} dp_{k, x, i} \times \left\{\sum_{L = 1}^{n} {L \choose i}\right\} \times i! \\ = & \sum_{i = 1}^{\log k} dp_{k, x, i} \times \left\{{n + 1 \choose i + 1}\right\} \times i! \\ \end{align*}\]

主要问题是 \(\displaystyle {n + 1 \choose i + 1}\) 怎么计算

你发现拆开之后

\[\begin{align*} & {n + 1 \choose i + 1} \\ = & \frac{(n - i + 1) \times (n - i + 2) \times \cdots \times (n + 1)} {(i + 1)!} \end{align*}\]

\((i + 1)!\) 逆元预处理, 上面的暴力计算即可


发现这种做法的 \(\rm{dp}\) 即使滚动了, 为了清空间一定要爆时间, 做法错误, 仅用来提供思路

代码

#include <bits/stdc++.h>
#define int long long
const int MAXLOGK = 30;
const int MAXN = 1e5 + 20;

const int MOD = 998244353;
namespace calc {
    int add(int a, int b) { return (a + b) > MOD ? a + b - MOD : a + b; }
    int sub(int a, int b) { return (a - b) < 0 ? a - b + MOD : a - b; }
    int mul(int a, int b) { return (a * b) % MOD; }
    void addon(int &a, int b) { a = add(a, b); }
    void mulon(int &a, int b) { a = mul(a, b); }
} using namespace calc;

int k, n;

int qpow(int x, int pows) {
    int res = 1;
    while (pows) { if (pows & 1) mulon(res, x); mulon(x, x), pows >>= 1; }
    return res;
}

int fac[MAXLOGK];
int inv[MAXLOGK];
/*一车预处理*/
void init() {
    fac[0] = 1; for (int i = 1; i <= 25; ++i) fac[i] = mul(fac[i - 1], i);
    inv[25] = qpow(fac[25], MOD - 2); for(int i = 24; ~i; --i) inv[i] = mul(inv[i + 1], i + 1);
}

int dp[2][MAXN][MAXLOGK];
int now = 0, nxt = 1;
/*处理 dp*/
void solve() {
    memset(dp, 0, sizeof dp);
    dp[now][1][0] = 1;

    for (int i = 2; i <= k; i++) {
        std::swap(now, nxt);
        memset(dp[now], 0, sizeof dp[now]);
        for (int xi = 0, lsy = 1; xi <= 20 && lsy <= k; xi++, lsy *= i) {
            for (int p = 0; p <= 20 - xi; p++) for (int j = 1; j * lsy <= k; j++) {
                addon(dp[now][lsy * j][p + xi], mul(dp[nxt][j][p], inv[xi]));
            }
        }
    }
}

/*龟速 C*/
int C(int u, int v) {
    int ans = 1;
    for (int i = u - v + 1; i <= u; i++) mulon(ans, i);
    return mul(ans, inv[v]);
}

signed main()
{
    init();

    int t; scanf("%lld", &t);
    while (t--) {
        scanf("%lld %lld", &k, &n);

        solve();

        printf("%lld ", n);
        for (int x = 2; x <= k; x++) {
            int ans = 0;
            for (int i = 1, lsy = 1; lsy <= k; i++, lsy *= 2) addon(ans, mul(mul(fac[i], dp[now][x][i]), C(n + 1, i + 1)));
            printf("%lld ", ans);
        }
        printf("\n");
    }

    return 0;
}

总结

多重集排列魅力时刻

一类常见的 \(\rm{dp}\) 方式
属于套路吧

遇到长得像组合数形式的考虑配成组合数形式

永远要相信自己, 心态要冷静, 耐心一点
时间分配还需要更冷静一点

posted @ 2025-01-20 16:46  Yorg  阅读(40)  评论(0)    收藏  举报