1. 题面梗概
中译中:定义一种 “221 序列” ,每个数字 x 必须连续出现恰好 x 次。给一个长度为 N 的数组 A,求其中有多少个不同的 221 子序列。
N≤5×105,答案对 998244353 取模。
2. 逻辑推导
221 序列的本质上是由若干个数值块组成的。一个值为 x 的块必须包含且仅包含 x 个 x。
{1},{2,2},{3,3,3}
我们的目标是统计不同的子序列。在子序列计数问题中,为了避免重复统计,对于同一种序列,只在它第一次能被构造出来的地方进行统计。
2.1 定义
定义:dp[i] 为以 A[i] 为最后一个元素,且 A[i] 刚好完成了一个数值块的不同子序列数量。
假设 A[i]=x,若要让 A[i] 作为一个块的终点,我们必须在 i 之前找 x−1 个值为 x 的元素。
2.2 转移
记录数值 x 出现的所有下标序列为 pos[x]。设当前 A[i] 是 x 在原数组中第 cnt 次出现(从 1 开始计数):
-
确定边界:当前块的起点下标(即该块中第一个 x 的位置)必须是 pos[x][cnt−x]。记为 r。
-
确定转移区间 [l,r−1]:
如果转移点 j<l,说明我们完全可以用 pos[x][cnt−x−1](即第 cnt−x 个 x)替换掉当前的 pos[x][cnt−1](即第 cnt 个 x),从而在更早的位置构造出同样的子序列。
- 因此,有效的转移来源区间被限定在 (l,r) 之间。即 j∈[l,r−1]。
2.3 前缀和优化
设 sum[i]=∑k=0idp[k],则状态转移方程就简化为:
dp[i]=sum[r−1]−sum[l]
注:dp[0]=1 表示空序列为状态起点,最后减去这个不合法区间。
3. 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| #include <bits/stdc++.h> using namespace std; using ll = long long;
const int maxn = 5e5 + 5; const int mod = 998244353;
vector< int > pos[maxn]; ll sum[maxn]; ll dp[maxn]; int a[maxn]; int n;
void solve( ) { cin >> n; for( int i = 1; i <= n; ++ i ) cin >> a[i];
sum[0] = dp[0] = 1;
for( int i = 1; i <= n; ++ i ) { int x = a[i]; pos[x].push_back( i );
int cnt = pos[x].size( );
if( cnt >= x ) { int r = pos[x][cnt - x]; int l = 0; if( cnt - x > 0 ) l = pos[x][cnt - x - 1]; ll cur = sum[r - 1]; if( l != 0 ) cur = ( cur - sum[l] + mod ) % mod;
dp[i] = cur; }
sum[i] = ( sum[i - 1] + dp[i] ) % mod; }
ll ans = ( sum[n] - 1 + mod ) % mod; cout << ans << '\n'; }
int main( ) { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); solve( ); return 0; }
|
4. 复盘