AT_wtf22_day2_d Cat Jumps 题解

AT_wtf22_day2_d Cat Jumps 题解

AT_wtf22_day2_d Cat Jumps

好牛的数学题!


首先将所有 \(a_i\) 看成有相对顺序的,然后最后将答案除以每个数出现次数的阶乘。(注意 \(-1\) 之间仍然没有相对顺序)

然后因为是要求恰好 \(k\) 个硬币,我们可以求钦定 \(k\) 个硬币的方案,再使用二项式反演即可求得恰好的方案。一个序列是恰好获得 \(k\) 个硬币的,相当于可以将序列划分为 \(k\) 段,满足每一段都是和为 \(0\) 的极小连续段,即不能再划分下去。

\(f_k\) 表示钦定 \(k\) 个硬币的答案,就是将序列划分为了 \(k\) 段满足每一段的和为 \(0\),但不用满足每一段都是极小的。于是我们可以枚举将 \(n\) 个数划分为 \(k\) 个集合的方案,那么对于一个集合 \(S\),相当于把集合 \(S\) 中的 \(a_i\) 划分为了一段。设 \(sum = \sum\limits_{i\in S}a_i\),这一段中就有 \(sum\)\(-1\),于是这一段的方案数为 \((sum+1)(sum+2)\ldots(sum+|S|)\)。这一个划分方案的贡献为所有段的方案数的乘积再乘上 \(k!\)

注意最后二项式反演时容斥系数为 \((-1)^{j-i} {j-1\choose i-1}\)。这个做法复杂度为 \(\mathcal{O}(B_nn)\),其中 \(B_n\) 表示贝尔数,即 \(n\) 个数的集合划分方案,也等于 \(\sum\limits_{i=0}^n{n\brace i}\)

继续优化,考虑这个式子的组合意义,相当于一个完全图,\(i\)\(j\) 有边权为 \(a_j+[j\le i]\) 的边,一种划分方案的贡献为每个点向同一个集合内的其中一个点连边,所有连边方案的边权的乘积之和。因为一个集合内编号第 \(i\) 小的点向集合内所有点连边的边权和为 \(sum+i\)

那么最终连出来的图是一个内向基环树森林,假设每个点可以向任意点连边,设 \(g_k\) 表示最终有 \(k\) 个基环树的方案,那么一个 \(g_i\)\(f_j\) 的贡献为将这 \(i\) 个基环树划分为 \(j\) 个集合的方案数,即 \(i\brace j\)。所以求出 \(g_i\) 后就能通过 \(f_i = \sum\limits_{j=i}^n g_j{j\brace i}\) 来求出 \(f_i\)

现在考虑求 \(g_k\),我们可以钦定有多少个环,设钦定有 \(k\) 个环的所有图的权值和为 \(h_k\),对 \(h\) 进行一次二项式反演即可得到 \(g\)。现在假设我们钦定了 \(k\) 个环,那么环外的点就可以向任意点连边,所以一个环外的点 \(x\) 的贡献为 \(x+\sum\limits_{i=1}^na_i\)。环上的边权仍然是 \(a_j+[j\le i]\),我们可以把这个式子写成 \((a_j+1)-[j>i]\)

考虑组合意义,因为是所有边权乘起来,所以相当于在每个 \((a_j+1)-[j>i]\) 的式子中选择其中一项。那么对于一段选择了 \(-1\) 的连续段,就对应了一条编号递增的链。换句话说,如果选择了若干条编号递增的链,就有每条链的链头 \(j\) 的贡献为 \(a_j\),其余点的贡献为 \(-1\),然后再将这些链拼成 \(k\) 个环。假设我们求出了选择 \(i\) 条链的权值和,那么拼成 \(j\) 个环的方案数相当于将 \(i\) 条链划分为 \(j\) 个集合,每个集合 \(S\) 的贡献为 \((|S|-1)!\),可以发现这就是 \({i\brack j}\)。于是我们求出选择 \(k\) 条链的权值和之后,乘第一类斯特林数即可得到 \(h_k\)

现在考虑求选择 \(k\) 条链的权值和。设 \(dp_{i,j}\) 表示编号前 \(i\) 的点,拼成 \(j\) 条链的权值和,那么对于第 \(i\) 个点分三种情况讨论:

  • 单独成一条链,链头贡献为 \(a_i\)\(dp_{i,j}\larr dp_{i-1,j-1}a_i\)
  • 接在某一条链的后面,贡献为 \(-1\)\(dp_{i,j}\larr dp_{i-1,j}\times (-j)\)
  • 放在环外,贡献为 \(i+\sum\limits_{j=1}^n a_j\)\(dp_{i,j}\larr dp_{i-1,j}(i+\sum\limits_{j=1}^n a_j)\)

于是整个问题可以在 \(\mathcal{O}(n^2)\) 内求出。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#define ll long long
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
using namespace std;
const ll N = 5005,mod = 998244353;
int tong[N],n;ll a[N],s;
ll fac[N],inv[N],f[N],g[N],S1[N][N],S2[N][N],C[N][N],dp[N][N],d = 1;
map<int,int> mp;
void init()
{
    dp[0][0] = S1[0][0] = S2[0][0] = C[0][0] = fac[0] = inv[0] = inv[1] = 1;
    for(int i = 2;i <= n;i++)inv[i] = (mod-mod/i)*inv[mod%i]%mod;
    for(int i = 1;i <= n;i++)fac[i] = fac[i-1]*i%mod;
    for(int i = 1;i <= n;i++)for(int j = C[i][0] = 1;j <= i;j++)
    {
        C[i][j] = (C[i-1][j-1]+C[i-1][j])%mod;
        S1[i][j] = (S1[i-1][j-1]+S1[i-1][j]*(i-1))%mod;
        S2[i][j] = (S2[i-1][j-1]+S2[i-1][j]*j)%mod;
    }
}
char buf[1<<21],*p1,*p2;
inline int rd()
{
    char c;int f = 1;
    while(!isdigit(c = getchar()))if(c=='-')f = -1;
    int x = c-'0';
    while(isdigit(c = getchar()))x = x*10+(c^48);
    return x*f;
}
int main()
{
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    n = rd();init();
    for(int i = 1;i <= n;i++)d = d*inv[++mp[a[i] = rd()]]%mod,(s += a[i]) %= mod;
    for(int i = 1;i <= n;i++)for(int j = 0;j <= i;j++)
        dp[i][j] = ((j?dp[i-1][j-1]*(a[i]+1):0)+dp[i-1][j]*(s+i-j))%mod;
    for(int i = 0;i <= n;i++)for(int j = i;j <= n;j++)
        (g[i] += dp[n][j]*S1[j][i]) %= mod;
    for(int i = n;~i;i--)for(int j = i+1;j <= n;j++)
        (g[i] -= g[j]*C[j][i]) %= mod;
    for(int i = 1;i <= n;f[i] = f[i]*fac[i]%mod,i++)for(int j = i;j <= n;j++)
        (f[i] += g[j]*S2[j][i]) %= mod;
    for(int i = n;i;i--)for(int j = i+1;j <= n;j++)
        (f[i] -= f[j]*C[j-1][i-1]) %= mod;
    for(int i = 1;i <= n;i++)printf("%lld\n",(f[i]+mod)*d%mod);
    return 0;
}
posted @ 2025-06-16 12:00  max0810  阅读(34)  评论(1)    收藏  举报