专栏文章
题解:AT_wtf22_day2_d Cat Jumps
AT_wtf22_day2_d题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mioxdcas
- 此快照首次捕获于
- 2025/12/03 02:42 3 个月前
- 此快照最后确认于
- 2025/12/03 02:42 3 个月前
为了方便,认为相同的 是本质不同的,最后将答案除以每种 出现次数的阶乘即可。
考虑计算钦定回到原点 次的方案数,使用经典容斥即可反推答案。而这等价于将 序列分为 组,每组内再用 将总和补成 。对于一个分组方案,记第 组数的总和为 ,数量为 ,对应的方案数即为 。记钦定意义下,将 序列分为 组的总方案数为 。
直接计算只能止步于指数复杂度,考虑构造组合意义进行优化。
将 从小到大排序,考虑一个有向完全图,对于所有的 , 向 连一条边权为 的边,那么一组数内的方案数 就可以被刻画成:将组内的每个 向恰好一个同组的 连边的代价之和,其中代价为边权之积。(考虑组内第 大的数,它与组内其它人的边的权值之和正好为 。)
由于同一组内一定会连成若干内向基环树,我们不妨换个角度处理问题,统计将 个点连成 棵内向基环树的方案数 ,乘以第二类斯特林数 即为对 的贡献。
棵基环树等价于有 个环,我们这里再次进行容斥:钦定 个点连出了 个环,不在环上的点可以向任意点(包括自己)连边。环外点有可能连出新的环,所以这里仍然需要容斥。
记 ,则环外点的连边方案数即为 。考虑环内边的权值,我们记环上点 的上一个结点为 ,那么边的权值即为 。我们将其拆为 和 两部分。考虑求出将 个点划分为 条上升链的方案数,其中每条链的权值按如下方法求积计算:链头提供 的权值,其余点均提供 的权值。将其乘以第一类斯特林数 ,即将链拼成环的方案数,所得即为对 的贡献。
另一个方向:考虑将上升链拼成环的过程中,我们强制令每条上升链都是极长的。那么再次考虑容斥,钦定一些链头前面的点是更小的,推导后将得到与上述分析相同的结果。
转化到上升链后,终于可以 DP 了。记前 个点连成了 条链的方案数为 ,那么可以得到如下转移:
分别对应了将点作为一条新链的开头,作为环外点和接到已有的链后方三种情形。
按前面的分析反演回去即可,时间复杂度 。
组合意义天地灭。
CPP#include <bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int mod = 998244353, N = 5000 + 5;
namespace basic {
int add(int x, int y) {return (x + y >= mod ? x + y - mod : x + y);}
int dec(int x, int y) {return (x - y < 0 ? x - y + mod : x - y);}
void ad(int &x, int y) {x = add(x, y);}
void de(int &x, int y) {x = dec(x, y);}
int neg(int x) {return x ? mod - x : 0;}
int qpow(int a, int b) {
int r = 1;
while (b) {
if (b & 1) r = 1ll * r * a % mod;
a = 1ll * a * a % mod; b >>= 1;
}
return r;
}
int inv(int x) {return qpow(x, mod - 2);}
int fac[N], ifac[N];
void fac_init(int n = N - 1) {
fac[0] = 1;
for (int i = 1; i <= n; i++)
fac[i] = 1ll * fac[i - 1] * i % mod;
ifac[n] = inv(fac[n]);
for (int i = n - 1; i >= 0; i--)
ifac[i] = 1ll * ifac[i + 1] * (i + 1) % mod;
}
int binom(int n, int m) {
if (n < m || m < 0) return 0;
return 1ll * fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
}
using namespace basic;
int n, a[N];
int S1[N][N], S2[N][N];
int h[N][N], H[N], R[N];
int f[N], g[N], cnt[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
fac_init();
cin >> n;
S1[0][0] = S2[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
S1[i][j] = add(S1[i - 1][j - 1], 1ll * (i - 1) * S1[i - 1][j] % mod);
S2[i][j] = add(S2[i - 1][j - 1], 1ll * j * S2[i - 1][j] % mod);
}
}
int sum = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
sum += a[i], cnt[a[i]]++;
}
sort(a + 1, a + n + 1);
h[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= i; j++) {
ad(h[i][j], 1ll * h[i - 1][j] * (sum + i - j) % mod);
ad(h[i][j], 1ll * h[i - 1][j - 1] * (a[i] + 1) % mod);
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
ad(H[j], 1ll * S1[i][j] * h[n][i] % mod);
}
}
for (int i = 1; i <= n; i++) {
for (int j = i, coe = 1; j <= n; j++, coe = mod - coe) {
ad(R[i], 1ll * coe * binom(j, i) % mod * H[j] % mod);
}
}
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
ad(f[i], 1ll * R[j] * S2[j][i] % mod * fac[i] % mod);
}
}
int Inv = 1;
for (int i = 1; i <= 5000; i++) {
Inv = 1ll * Inv * ifac[cnt[i]] % mod;
}
for (int i = 1; i <= n; i++) {
for (int j = i, coe = 1; j <= n; j++, coe = mod - coe) {
ad(g[i], 1ll * coe * binom(j - 1, i - 1) % mod * f[j] % mod);
}
cout << 1ll * g[i] * Inv % mod << "\n";
}
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...