一、题目
二、解法
下次再也不找这种阴间题做了,根本想不到好吗?
首先做一个简单的转化:考虑让 (k-1) 第一次出现的位置大于 (k) 最后一次出现的位置。
考虑构造映射去描述好序列,你发现转化后的条件是比较连贯的,因为 (k-1) 第一次出现的位置大于 (k) 最后一次出现的位置,而 (k) 第一次出现的位置大于 (k+1) 最后一次出现的位置。
所以我们构建的映射要能很好的描述位置这一信息,那么考虑把 (1) 的出现位置从大到小写下,(2) 的出现位置从大到小写下(...)然后你发现写完这不是一个排列吗?那我们映射把好序列映射到一个排列上去了!
再证明所有排列都能映射回一个好序列,我们把原排列划分成若干个极长下降子段,第一个子段就代表 (1) 的所有出现位置,第二个子段就代表 (2) 的所有出现位置(...)以此类推就能映射到好序列上去。
那么得到结论:好序列能和排列一一映射
剩下的问题就是计数了,不难看出这是一个贡献法,我们考虑在排列上计数,考虑前面有 (j-1) 个下降子段那么这个数就是 (j),那么我们考虑每个位置的贡献,把前面和后面分开,先选出 (i) 个数放在前面,后面的数随便排列,然后前 (i) 个数的方案中需要存在 (j-1) 对 (a_i<a_{i+1}):
这里补充一下前置芝士,(E(n,k)) 表示 (n) 个数的排列有 (k) 对 (a_i<a_{i+1}) 的方案数,又称欧拉数,这个东西可以递推,考虑在 (n-1) 个数的基础上新插入一个 (n) 的影响:
- 如果不会带来相邻顺序对的新增,那么可以插入在原来 (k) 对的空隙,或者插入到序列的开头,有 (k+1) 种方案,从 (E(n-1,k)) 处转移而来。
- 如果会使相邻顺序对增加 (1),那么不能在原来 (k-1) 对的空隙中插入,也不能在序列的开头处插入,有 (n-k) 种方案,从 (E(n-1,k-1)) 处转移而来。
所以 (E(n,k)=(k+1)cdot E(n-1,k)+(n-k)cdot E(n-1,k-1))
三、总结
计数中重要的方法是映射法,考虑原问题能不能映射到一些简单的模型中。此外,本题重要的是一个存在性限制,但不能转化所有性限制,转化为单点性限制就好做了很多。
插入法是解决排列计数的重要方法,通常是插入新增的元素考虑它的影响。
#include <cstdio>
const int M = 5005;
const int MOD = 998244353;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,o[M][M],fac[M],inv[M];
void init()
{
fac[0]=inv[0]=inv[1]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
}
signed main()
{
n=read();init();
for(int i=1;i<=n;i++)
o[i][0]=1;
for(int i=2;i<=n;i++)
for(int j=1;j<i;j++)
o[i][j]=o[i-1][j]*(j+1)+o[i-1][j-1]*(i-j),o[i][j]%=MOD;
for(int j=1;j<=n;j++)
{
int ans=0;
for(int i=j;i<=n;i++)
ans=(ans+o[i][j-1]*fac[n]%MOD*inv[i])%MOD;
printf("%lld ",ans);
}
}