CF1349F1 Slime and Sequences
一、题目
二、解法
下次再也不找这种阴间题做了,根本想不到好吗?
首先做一个简单的转化:考虑让 \(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);
}
}