[每日 D] Multiplicative Arrays
前言
不求晓得这什么难度, 姑且假定是 \(\textrm{div 2 D}\)
观前提示: 这是假解, 仅供参考
思路
初步观察
你发现我们可以看做求这样一组 \(x_i\) 表示一个数字出现的次数, 使得
然后这样一组 \(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]\)
贡献还是
转移, 初始化 \(dp_{1, 1, 0} = 1\)
容易发现枚举 \(x_i\) 是 \(\log k\) 的, 枚举 \(p\) 是 \(\log k\) 的
枚举 \(j\) 比较特殊, 对于一个确定的 \(x_i\) , 复杂度是 \(\frac{k}{i^{x_i}}\) 的
总复杂度只能证明是 \(k \log k \sim k^2\) 的, 不过感性理解是不大的
推的很乱, 整理一下
目标是找到一组分配 \(x\) , 使其满足
然后计算方法
转移, 初始化 \(dp_{1, 1, 0} = 1\)
计算答案
考虑答案计算
主要问题是 \(\displaystyle {n + 1 \choose i + 1}\) 怎么计算
你发现拆开之后
\((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}\) 方式
属于套路吧
遇到长得像组合数形式的考虑配成组合数形式
永远要相信自己, 心态要冷静, 耐心一点
时间分配还需要更冷静一点

浙公网安备 33010602011771号