CF1349F1 Slime and Sequences (Easy Version) 题解

Description

传送门

Solution

Part 1: 性质观察

遇到这种输入量非常小的题,我们往往会想到找规律。先用暴力打个表出来试试吧。

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 a a 而言,将 a a a 中的元素从大到小排序;特别的,若两个元素的值相等,那么按照下标从大到小排序。将排序后的序列中的各个元素原来所在的位置依次取出,即可得到一个长度为 n n n 的排列 p p p
  • 对于一个排列 p p p 而言,我们将其划分为若干极长下降区间。对于第 k k k 个下降区间 [ l , r ] [l,r] [l,r] ∀ i ∈ [ l , r ] \forall i \in [l,r] i[l,r] a p i = k a_{p_i}=k api=k,即可得到一个长度为 n n n 的好序列 a a a

因此, a n s i ans_i ansi 即为所有长度为 n n n 的排列的第 i i i 个极长下降区间的长度之和。

Part 2: 列出式子

注意到,对于一个排列 p p p 中的第 i i i 位,其会对 a n s x ans_x ansx 产生贡献当且仅当 [ 1 , i ) [1,i) [1,i) 中恰有 x − 1 x-1 x1 个位置 k k k 使 p k < p k + 1 p_kpk<pk+1

于是得到式子

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=1nfp,i1(pn)(np)!

其中, f p , i − 1 f_{p,i-1} fp,i1 表示长度为 p p p 且有 i − 1 i-1 i1 个上升的排列数, ( n p ) ( n − p ) ! {n \choose p}(n-p)! (pn)(np)! 表示在 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

Part 3: 动态规划

考虑使用 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。则有以下几种可能:

  • 插入在了 p t , p t + 1 ( 1 ≤ t < x ) p_t,p_{t+1}(1 \le tpt,pt+1(1t<x) 之间且 p t > p t + 1 p_t>p_{t+1} pt>pt+1。此时状态轮廓变为 f x + 1 , y + 1 f_{x+1,y+1} fx+1,y+1,且这样的 t t t x − 1 − y x-1-y x1y 个。
  • 插入在了 p t , p t + 1 ( 1 ≤ t < x ) p_t,p_{t+1}(1 \le tpt,pt+1(1t<x) 之间且 p t < p t + 1 p_tpt<pt+1。此时状态轮廓变为 f x + 1 , y f_{x+1,y} fx+1,y,且这样的 t t t y y y 个。
  • 插入在了最开头。状态轮廓变为 f x + 1 , y f_{x+1,y} fx+1,y
  • 插入在了最末尾。状态轮廓变为 f x + 1 , y + 1 f_{x+1,y+1} fx+1,y+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×(xy)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 就是欧拉数。具体可见这篇文章。

Code

#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;
}

你可能感兴趣的:(动态规划,排序算法,算法)