The Journey of Geor Autumn 上海ICPC 2020 E dp新姿势
题目大意:
求大小是 (n) 且满足条件的排列有多少,条件是:给定k,对于任意 (i>k \,\,&\,\,i<=n) 的 (i) ,存在 (a_i>min(a_{i-k},...,a_{i-1}))
题解:
首先先去试着构造这个排列,首先明确1一定在前面k个数中,任意选择一个位置放1,那么2可以在1的左边,此时可以理解为2前面比她小的是0,也可以放在距离1不超过 (k) 的右边,假设1和2相差 (j) ,那么1和2中间可以填 (j-1) 个数,这些数是没有任何限制的,如果3已经被填了,那么继续往后考虑其他的数,以这样的构造思维来定义这个 (dp) 。
先写出复杂度是 (O(nk)) 的算法,定义 (dp[i]) 表示
- 表示已经放了前 i 个位置
- i 这个位置的值小于当前还没有放的所有值
- 往前推 j 个, i 这个位置的值也是最小的
这样最后答案就是 (dp[n])
给出 (dp) 定义之后可以自己写转移方程了
(dp[i] += dp[i-j]*A(n-i+j-1,j-1)%mod;)
(dp[i]) 从 (dp[i-j]) 转移过来,说明 (dp[i-j]<dp[i])
(A(n-i+j-1,j-1)) 因为从 (i-j) 到 (i) 一共有 (j) 个数,中间可以填(j-1)个数,底数是 (n-i+j-1) 表示的是从 (i-j) 到 (n) 还有 (n-i+j) 个数,但是因为 (i) 这个位置的值既要小于当前还没有放的所有值,又要是往前推 (j) 个的最小值,所以 (i) 这个位置的值一定是最小值,所以要去掉这个值,所以只有 (n-i+j-1) 这么多个数的选择。
dp[0] = 1;
for(int i=1;i<=n;i++){
for(int j=1;j<=min(i,k);j++){
dp[i] += dp[i-j]*A(n-i+j-1,j-1)%mod;
dp[i] %= mod;
}
}
这样的算法还是不行,需要优化,优化考虑去拆A这个排列,然后发现:
还要注意一点的是对于每一个 (i) 最多只能考虑前面 (k) 个数,所以对 (i>k) 的 (dp) 要删去前面的最后一个值。
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define inf64 0x3f3f3f3f3f3f3f3f
using namespace std;
const int maxn = 1e7+10;
typedef long long ll;
const int mod = 998244353;
ll dp[maxn],inv[maxn],fac[maxn];
void init() {
fac[0] = 1, fac[1] = 1,inv[1] = 1;
for (int i = 2; i < maxn; i++) {
fac[i] = fac[i - 1] * i % mod;
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
}
inv[0] = 1;
for (int i = 1; i < maxn; i++) {
inv[i] = inv[i - 1] * inv[i] % mod;
}
}
/*
* 最小的数是0,所以在0和1之间夹很多数,所以1不一定在1这个位置,因为dp[2]也可以从dp[0]转移过来
*/
int main(){
init();
int n,k;
scanf("%d%d",&n,&k);
dp[1] = dp[0] = 1;
for(int i=2;i<=n;i++){
dp[i] = dp[i-1]*(n-i+2)%mod;
if(i>k){
ll res = fac[n-i+k] * inv[n-i] % mod;
dp[i] = (dp[i] - res * dp[i - k - 1]%mod + mod)%mod;
}
}
printf("%lld
",dp[n]);
return 0;
}
/*
dp[i] = dp[i-1]
+ dp[i-2]*(n-i+1)
+ dp[i-3]*(n-i+1)*(n-i+2)
dp[1] = dp[0]
dp[2] = dp[1]
+ dp[0]*(n-1)
dp[3] = dp[2]
+ dp[1]*(n-2)
+ dp[0]*(n-1)*(n-2)
*/