题目描述

我们定义独特子序列:如果一个序列的某个连续子序列 al, al+1, , ar 中恰好包含 k 个奇数,就称序列 a[lr] 是一个独特子序列。

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

限制:

  • 对于 40% 的数据:1n100ai1
  • 对于 70% 的数据:1n5000ai1
  • 对于 100% 的数据:1k2×105105ai105

算法分析

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

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

70 分做法:在 40 分做法中需要一重循环统计 [ij] 之间的奇数个数,可以利用前缀和预处理,然后在 O(1) 时间之内得到。定义 si[1i] 中奇数的个数,则 sisi1 递推而来,即:si=si1+(ai%20).
时间复杂度:O(n2)

100 分做法:在 70 分做法中,实际上在统计 sjsi1=k 的个数。对于这个式子简单移项可得:si1=sjk

所以我们考虑以 j 结尾的 独特子序列 个数时,只要统计有多少个奇数个数为 sjksi1 个数即可。我们只要建立频次数组 cnt 记录 sj 出现的次数,从左往右更新 cnt 边计算答案,那么以 j 结尾的答案 cnt[sjk] 即可在 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;
}