传送门
遇到这种输入量非常小的题,我们往往会想到找规律。先用暴力打个表出来试试吧。
1: 1
2: 3 1
3: 10 7 1
4: 41 39 15 1
我们发现,输入 n n n 后对应的答案之和为 n ! n n!n n!n;因此,好序列的数量恰有 n ! n! n! 种。这启发我们将好序列与长度为 n n n 的排列建立双射关系。
考虑构造下面的双射:
因此, a n s i ans_i ansi 即为所有长度为 n n n 的排列的第 i i i 个极长下降区间的长度之和。
注意到,对于一个排列 p p p 中的第 i i i 位,其会对 a n s x ans_x ansx 产生贡献当且仅当 [ 1 , i ) [1,i) [1,i) 中恰有 x − 1 x-1 x−1 个位置 k k k 使 p k < p k + 1 p_k
于是得到式子
a n s i = ∑ p = 1 n f p , i − 1 ( n p ) ( n − p ) ! ans_i=\sum_{p=1}^n f_{p,i-1} {n \choose p} (n-p)! ansi=p=1∑nfp,i−1(pn)(n−p)!
其中, f p , i − 1 f_{p,i-1} fp,i−1 表示长度为 p p p 且有 i − 1 i-1 i−1 个上升的排列数, ( n p ) ( n − p ) ! {n \choose p}(n-p)! (pn)(n−p)! 表示在 n n n 个数中选择 p p p 个放到该前缀中并将剩下的部分随意排列的方案数。
如果我们求出了每个 f i , j f_{i,j} fi,j,那么我们就可以在 O ( n 2 ) O(n^2) O(n2) 的时间复杂度内求出所有的 a n s i ans_i ansi。现在关键在于求出每个 f i , j f_{i,j} fi,j。
考虑使用 dp \text{dp} dp 求出所有的 f x , y f_{x,y} fx,y。
考虑在状态轮廓 f x , y f_{x,y} fx,y 中插入 x + 1 x+1 x+1。则有以下几种可能:
从而,得到转移:
f x , y × ( x − y ) → f x + 1 , y + 1 f_{x,y} \times (x-y) \to f_{x+1,y+1} fx,y×(x−y)→fx+1,y+1 f x , y × ( y + 1 ) → f x + 1 , y f_{x,y} \times (y+1) \to f_{x+1,y} fx,y×(y+1)→fx+1,y
于是我们在 O ( n 2 ) O(n^2) O(n2) 的复杂度内求出了每个 f f f。
其实,这里的 f x , y f_{x,y} fx,y 就是欧拉数。具体可见这篇文章。
#include
#define ll long long
using namespace std;
const int maxl=5005,mod=998244353;
int n;
int f[maxl][maxl],jc[maxl],inv[maxl],g[maxl],ans[maxl];
void chksum(int x,int &y){y=(x+y)%mod;}
int quick_power(ll x,int y){
ll res=1;
for (;y;y=y>>1,x=(x*x)%mod){
if (y&1) res=(res*x)%mod;
}
return res;
}
void init(){
f[0][0]=1,jc[0]=1;
for (int i=0;i<=n;i++){
for (int j=0;j<=i;j++){
chksum((ll)f[i][j]*(i-j)%mod,f[i+1][j+1]);
chksum((ll)f[i][j]*(j+1)%mod,f[i+1][j]);
}
}
for (int i=1;i<=n;i++) jc[i]=((ll)jc[i-1]*i)%mod;
inv[n]=quick_power(jc[n],mod-2);
for (int i=n-1;i>=0;i--) inv[i]=((ll)inv[i+1]*(i+1))%mod;
}
int C(int x,int y){
ll num=jc[x];
num=(num*inv[y])%mod;
num=(num*inv[x-y])%mod;
return num;
}
signed main(){
cin>>n;init();
for (int i=1;i<=n;i++) g[i]=C(n,i);
for (int i=1;i<=n;i++){
for (int j=max(1,i-1);j<=n;j++){
ll cur=f[j][i-1];
cur=(cur*g[j])%mod;
cur=(cur*jc[n-j])%mod;
chksum(cur,ans[i]);
}
}
for (int i=1;i<=n;i++) printf("%d ",ans[i]);
return 0;
}