[ARC146E] Simple Speed
简单的操作分析题。
一些基础想法
首先,注意到直接怼着序列,不好计数。考虑按照值域从小到大计数。
假若不考虑边界(或者我们令 \(B_0=B_{N+1}=N+1\)),那么每次的段数是固定的。
具体的,元素 \(1\) 的所有数形成了 \(a_1\) 个不能相交的段,其中每个段两端都需要两个元素 \(2\)。但是两个段假若相邻,则可以共用一个 \(2\) 元素。
容易发现,此时在完成所有元素 \(2\) 的插入后,必定是形如一些类似 212,2121212
以及单独 2
的段。
这些段都满足:每段的元素 \(2\) 个数都比元素 \(1\) 个数多 \(1\)。
故而每次容易算出有多少个新的段。而考虑到当前值域的所有元素和段时,我们往往只关心段数,因为这些段在后面计算的时候没有本质区别。
而如何组合拼接这些的段,可以用一个组合数直接完成。
正确的做法
但是现在考虑到两端都存在边界。
换句话说,比如当前有 \(5\) 个元素 \(1\) ,要插入 \(7\) 个元素 \(2\)。
按照上面的想法,一种可行方案是 21212,2121212
,它现在的段数是 \(a_2-a_1=7-5=2\)。
当然,实际上还存在其它分配方案,比如 1212,212,21212
。
但是在这种方案中,第一段它的两端就不全是 \(2\) 了,换句话说,它现在只能放在最终序列的左边界。
注意到边界只有左、右两个。
故而我们记 \(f_{i,j=0\sim 2,k}\) 表示,考虑了值域为 \(1\sim i\) 的所有数,左右边界中已经被占据的边界有 \(j\) 个,最终形成了 \(k\) 个两端都是元素 \(i\) 的段。
这里的 \(k\) 只有 \(O(1)\) 个,可以轻松通过。
代码
#include <bits/stdc++.h>
#define FL(i, a, b) for (int i = (a); i <= (b); ++i)
#define FR(i, a, b) for (int i = (a); i >= (b); --i)
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
constexpr int N = 2e5 + 10, V = 2e5;
constexpr int mod = 998244353;
int n, cnt[N];
int inv[N], fac[N], ifac[N];
unordered_map<int, int> f[N][3];
inline void AddTo(int &x, const int &y) {
x += y;
x -= (x >= mod? mod : 0);
}
void Init() {
fac[0] = ifac[0] = 1;
FL(i, 1, V) {
inv[i] = (i == 1? 1 : (ll)inv[mod % i] * (mod - mod / i) % mod);
fac[i] = (ll)fac[i - 1] * i % mod;
ifac[i] = (ll)ifac[i - 1] * inv[i] % mod;
}
}
int C(int n, int m) {
if (n < m || m < 0) {
return 0;
}
return (ll)fac[n] * ifac[n - m] % mod * ifac[m] % mod;
}
int main() {
Init();
scanf("%d", &n);
FL(i, 1, n) {
scanf("%d", &cnt[i]);
}
f[1][2][cnt[1] - 1] = 1;
FL(i, 2, n) {
FL(a, 0, 2) {
FL(c, a, 2) {
for (auto x: f[i - 1][c]) {
int u = x.fi + a, v = x.se;
if (cnt[i] < u) continue;
int tmp = (ll)v * C(cnt[i] - 1, u - 1) % mod;
if (c == 2 && a == 1) {
tmp = tmp * 2 % mod;
}
AddTo(f[i][a][cnt[i] - u], tmp);
}
}
}
}
int ans = 0;
FL(a, 0, 2) {
AddTo(ans, f[n][a][0]);
}
printf("%d\n", ans);
return 0;
}