📌  相关文章
📜  国际空间研究组织 | ISRO CS 2008 |问题 65(1)

📅  最后修改于: 2023-12-03 15:37:14.043000             🧑  作者: Mango

国际空间研究组织 | ISRO CS 2008 |问题 65

本题目来自印度国际航天局(ISRO)的计算机科学考试(2008年)。

题目描述

给定一个长度为 $n$ 的整数数组 $arr$ 和一个非负整数 $k$,需要找到所有的连续子数组中,恰好有$k$个数字是奇数的子数组数量。

输入格式

第一行输入一个整数 $n$,表示数组长度。

第二行输入 $n$ 个整数,表示数组 $arr$ 的元素。

第三行输入一个非负整数 $k$。

输出格式

输出一个整数,表示满足要求的连续子数组数量。

输入样例1:
5
1 2 2 1 2
1
输出样例1:
2
解释

满足条件的两个子数组分别为 [1,2] 和 [2,1]。

输入样例2:
4
4 4 4 4
0
输出样例2:
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)