CF1000D 又一个序列上的问题
1 CF1000D 又一个序列上的问题
2 题目描述
时间限制 \(2s\) | 空间限制 \(256M\)
如果 \(𝑎_1=𝑘−1\) 并且 \(𝑎_1>0\), 则由整数 \(𝑎_1,𝑎_2,…,𝑎_𝑘\) 构成的序列称为好序列。例如,序列 \([3,-1,44,0],[1,-99]\) 是好的序列,而序列 \([3,7,8],[2,5,4,1],[0]\) 就不是。
如果一个序列可以分成正数个好的序列,则称其为好序列。每个好的序列应该是序列的一个子段,并且序列的每个元素都应该完全属于一个序列。例如,\([2,-3,0,1,4],[1,2,3,-3,-9,4]\) 是好序列,而 \([2,-3,0,1],[1,2,3,−3,−9,4,1]\) 就不是。
对于给定的序列,计算其为好序列的子序列的数量,并模 \(998244353\) 输出此类子序列的数量。
3 题解
我们设 \(dp_i\) 表示以第 \(i\) 个数开始的序列数量,则有 \(dp_i = C_{n-i}^{a_i}\),意思就是从后面的那些数中选出 \(a_i\) 个数总共的情况数。但是这并不是全部情况,因为我们可以从当前位置开始选择 \(a_i\) 个数后,在后面再寻找一个好序列,将当前找到的数组插在开头使得整个序列成为一个好序列。
那么如何计算这一部分答案呢?我们发现:在后面找到的好序列一定是某个 \(dp\) 值,所以我们想到要倒序计算 \(dp\) 数组。而知道了这一点后,转移方程其实也就呼之欲出了:
在这里解释一下这个转移:由于 \(a_i \ge 1\),所以当 \(j = i+1\) 时我们要在 \(0\) 个数中找到 \(1\) 个及以上的数,显然不现实,所以从 \(i+2\) 开始。 \(j-i+1\) 就是 \(i\) 和 \(j\) 之间空的数量,在其中选 \(a_i\) 个的情况数乘上以第 \(j\) 个数为开始的序列数量,得到的就是以第 \(i\) 个数为开始的序列数量(当然不要忘了加上只由一个好数组组成的情况数,也就是 \(C_{n-i}^{a_i}\))。最终我们的答案也十分简单,就是 \(\sum_{i = 1}^n \limits dp_i\),以所有位置为开头的序列数量之和。
至于组合数,我们可以在开头利用 \(C_n^m = C_{n-1}^m + C_{n-1}^{m-1}\) 的公式预处理出,时间复杂度为 \(O(n^2)\)。
代码(空格警告):
#include <iostream>
using namespace std;
const int N = 1e3+10;
const int mod = 998244353;
#define int long long
int n, ans;
int a[N], dp[N], c[N][N];
signed main()
{
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) c[i][i] = 1, c[i][0] = 1;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j < i; j++)
{
c[i][j] = (c[i-1][j] % mod + c[i-1][j-1] % mod) % mod;
}
}
for (int i = n; i >= 1; i--)
{
if (a[i] <= 0) continue;
if (n - i >= a[i])
{
for (int j = i+2; j <= n; j++)
{
dp[i] = (dp[i] + (c[j-i-1][a[i]] * dp[j] % mod) % mod) % mod;
}
dp[i] = (dp[i] + c[n-i][a[i]]) % mod;
}
}
for (int i = 1; i <= n; i++) ans = (ans + dp[i]) % mod;
cout << ans;
return 0;
}