题目描述
我们定义独特子序列:如果一个序列的某个连续子序列 \(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;
}