📅  最后修改于: 2023-12-03 15:37:14.043000             🧑  作者: Mango
本题目来自印度国际航天局(ISRO)的计算机科学考试(2008年)。
给定一个长度为 $n$ 的整数数组 $arr$ 和一个非负整数 $k$,需要找到所有的连续子数组中,恰好有$k$个数字是奇数的子数组数量。
第一行输入一个整数 $n$,表示数组长度。
第二行输入 $n$ 个整数,表示数组 $arr$ 的元素。
第三行输入一个非负整数 $k$。
输出一个整数,表示满足要求的连续子数组数量。
5
1 2 2 1 2
1
2
满足条件的两个子数组分别为 [1,2] 和 [2,1]。
4
4 4 4 4
0
10
虽然整个数组中没有奇数,但是任意长度的数组的奇数数目都为0,所以全部都是满足条件的子数组。
要解决这个问题,首先需要明确一个事实,那就是对于任意连续子数组 $[l,r]$,其中奇数个数恰好等于 $k$ 时,我们可以得到另外一组连续子数组,使用类似滑动窗口(two-pointer)的方法,令其中恰好有 $k-1$ 个奇数的子数组的右端点为 $r1$,右端点为 $r$ 的恰好有 $k$ 个奇数的子数组一定包含子数组 $[l,r1]$。而在这个前提下,右端点为 $r$ 的恰好有 $k$ 个奇数的子数组数量可以通过计算 $[r1+1,r]$ 中奇数个数为 $1$ 的子数组数量来求得。所以,我们可以使用动态规划的方式,依次预处理出所有奇数个数为 $i$ 时的左右端点为 $l$ 和 $r$ 的满足条件的子数组的数量 $dp_{l,r,i}$。
根据前面的分析,我们可以发现,当 $i=k$ 时,满足条件的子数组的数量可以通过 $dp_{l,r,1},dp_{l,r,2},dp_{l,r,3}.....dp_{l,r,k}$ 之间的关系计算得到。具体来说,将数组中所有奇数的下标记录下来,设为 $pos_1,pos_2,.....pos_m$,对于每个 $pos_i$,我们找到区间 $[pos_{i-k+1},pos_i]$ 中奇数个数为 $k$ 的所有子数组,设这些子数组的右端点为 $r_i$,那么如果再定义 $r_{i-k+1},r_{i-k+2},.....r_m$ 分别为 $r_{pos_{i-k+1}},r_{pos_{i-k+2}},.....r_{pos_m}$,则满足条件的子数组数量为:
$$dp_{pos_{i-k+1},r_{i-k+1},1} \times dp_{pos_{i-k+2},r_{i-k+2},2} \times .... \times dp_{pos_i,r_i,k}$$
其中 $dp_{pos_{i-k+1},r_{i-k+1},1}$ 表示区间 $[pos_{i-k+1},r_{i-k+1}]$ 中恰好有 $1$ 个奇数的子数组数量。
显然计算 $dp_{l,r,i}$ 的时间复杂度为 $O(n^2k)$,计算最终结果的时间复杂度为 $O(mk)$,其中 $m$ 表示数组中奇数的个数。
代码如下:
l1 = list(map(int, input().split()))
n, a, k = l1[0], l1[1:], int(input())
odd, dp = [], [[[0] * 21 for _ in range(n)] for _ in range(n)]
ans = 0
# 求出奇数的下标
for i in range(n):
if a[i] % 2 == 1:
odd.append(i)
# 预处理 dp 数组
for i in range(n):
if a[i] % 2 == 1:
cnt = 1
else:
cnt = 0
for j in range(i + 1, n):
if a[j] % 2 == 1:
cnt += 1
dp[i][j][cnt] = 1
for i in range(n):
for j in range(i, n):
for x in range(1, k + 1):
dp[i][j][x] += dp[i][j][x - 1]
if i > 0:
dp[i][j][x] += dp[i - 1][j][x] - dp[i - 1][j][x - 1]
if j > 0:
dp[i][j][x] += dp[i][j - 1][x] - dp[i][j - 1][x - 1]
if i > 0 and j > 0:
dp[i][j][x] -= dp[i - 1][j - 1][x - 1]
# 计算最终结果
if k == 0:
print((n * (n + 1)) // 2)
else:
for i in range(k - 1, len(odd)):
l, r = odd[i - k + 1] if k > 1 else -1, odd[i]
ans += dp[l + 1][r - 1][k - 1] * dp[r][n - 1][1]
print(ans)
返回markdown格式的代码片段:
```python
l1 = list(map(int, input().split()))
n, a, k = l1[0], l1[1:], int(input())
odd, dp = [], [[[0] * 21 for _ in range(n)] for _ in range(n)]
ans = 0
# 求出奇数的下标
for i in range(n):
if a[i] % 2 == 1:
odd.append(i)
# 预处理 dp 数组
for i in range(n):
if a[i] % 2 == 1:
cnt = 1
else:
cnt = 0
for j in range(i + 1, n):
if a[j] % 2 == 1:
cnt += 1
dp[i][j][cnt] = 1
for i in range(n):
for j in range(i, n):
for x in range(1, k + 1):
dp[i][j][x] += dp[i][j][x - 1]
if i > 0:
dp[i][j][x] += dp[i - 1][j][x] - dp[i - 1][j][x - 1]
if j > 0:
dp[i][j][x] += dp[i][j - 1][x] - dp[i][j - 1][x - 1]
if i > 0 and j > 0:
dp[i][j][x] -= dp[i - 1][j - 1][x - 1]
# 计算最终结果
if k == 0:
print((n * (n + 1)) // 2)
else:
for i in range(k - 1, len(odd)):
l, r = odd[i - k + 1] if k > 1 else -1, odd[i]
ans += dp[l + 1][r - 1][k - 1] * dp[r][n - 1][1]
print(ans)