题目描述

我们定义独特子序列:如果一个序列的某个连续子序列 \(a_l, ~a_{l+1},~\ldots,~a_r\) 中恰好包含 \(k\) 个奇数,就称序列 \(a[l\ldots r]\) 是一个独特子序列。

给定一个长度为 \(n\) 的序列 \(a_1,a_2, \ldots, a_n\) 和一个整数 \(k\) 。请你计算在序列 \(a_1,a_2,\ldots,a_n\) 中一共包含多少个不同的独特子序列,只要序列的左端点和右端点至少有一个不同,就认为是两个不同的独特子序列。

限制:

  • 对于 \(40\%\) 的数据:\(1 \leqslant n \leqslant 100\)\(a_i \geqslant 1\)
  • 对于 \(70\%\) 的数据:\(1 \leqslant n \leqslant 5000\)\(a_i \geqslant 1\)
  • 对于 \(100\%\) 的数据:\(1 \leqslant k \leqslant 2 \times 10^5\)\(-10^5 \leqslant a_i \leqslant 10^5\)

算法分析

本题难度中等,考察枚举思想与前缀和技巧。考虑以 \(j\) 结尾的 独特子序列 个数,我们需要统计符合条件的下标 \(i\) 的个数,其中 \(1 \leqslant i \leqslant j\)\([i \cdots j]\) 这个子序列里的奇数个数恰好为 \(k\).

\(40\) 分做法:双重循环分别枚举左端点 \(i\) 和右端点 \(j\),然后再来一重循环统计 \([i \cdots j]\) 之间的奇数个数
时间复杂度:\(O(n^3)\)

\(70\) 分做法:在 \(40\) 分做法中需要一重循环统计 \([i \cdots j]\) 之间的奇数个数,可以利用前缀和预处理,然后在 \(O(1)\) 时间之内得到。定义 \(s_i\)\([1 \cdots i]\) 中奇数的个数,则 \(s_i\)\(s_{i-1}\) 递推而来,即:\(s_i = s_{i-1} + (a_i \% 2 \neq 0)\).
时间复杂度:\(O(n^2)\)

\(100\) 分做法:在 \(70\) 分做法中,实际上在统计 \(s_j - s_{i-1} = k\) 的个数。对于这个式子简单移项可得:\(s_{i-1} = s_{j} - k\)

所以我们考虑以 \(j\) 结尾的 独特子序列 个数时,只要统计有多少个奇数个数为 \(s_j - k\)\(s_{i-1}\) 个数即可。我们只要建立频次数组 cnt 记录 \(s_j\) 出现的次数,从左往右更新 \(cnt\) 边计算答案,那么以 \(j\) 结尾的答案 \(cnt[s_j - k]\) 即可在 \(O(1)\) 时间内得到。最后的答案即为所有下标结果的 独特子序列 个数之和。
时间复杂度:\(O(n)\)

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

int main() {
    int n, k;
    cin >> n >> k;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    vector<int> s(n+1);
    rep(i, n) s[i+1] = s[i] + (a[i]%2 != 0);
    
    ll ans = 0;
    vector<int> cnt(n+1);
    cnt[0] = 1;
    for (int i = 1; i <= n; ++i) {
        if (s[i] >= k) ans += cnt[s[i]-k];
        cnt[s[i]]++;
    }
    
    cout << ans << '\n';
    
    return 0;
}