📜  门|门CS 2013 |第 50 题(1)

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

题目

题目链接:门|门CS 2013 |第 50 题

题目描述

给你一个长为 $n$ 的01串 $s$,再给你一个数组 $a$,$a_i∈[1,n]$ 表示 $s$ 的某一个位置可以改变原来的数字,结果只能是改成 $i$。 要求你改变 $s$ 中最少的数字,使得 $s$ 中每个位置为 1 的左右两边都有一个位置为 0。 输出最小的改变次数。

输入输出格式
输入格式

第 $1$ 行,一个正整数 $n$。

第 $2$ 行,一个长为 $n$ 的01串 $s$,保证 $s$ 中至少有 $2$ 个0。

第 $3$ 行,一个正整数 $m$,表示数组 $a$ 的长度。

第 $4-3+m$ 行,每行一个正整数 $a_i$ ,保证不重复。

输出格式

输出一个整数,表示最小的改变次数。

输入输出样例
输入样例 #1
10
1110100101
2
2
7
输出样例 #1
1
说明
输入输出样例解释

将第 $2$ 个 $1$ 改为 $0$ 即可。

程序分析

本题可以使用贪心算法求解:

  1. 统计所有满足要求的 01 段;
  2. 对于每个未覆盖的 01 段,找到左右两边距离最短的满足要求的 01 段;
  3. 标记这两个 01 段已被覆盖;
  4. 重复步骤 2 - 3,直到所有的 01 段都已被覆盖。

在第 2 步中,找到的左右两边的 01 段可以使用二分查找来加速。

时间复杂度为 $O(m\log m)$,空间复杂度为 $O(m)$。

代码实现

n = int(input())
s = input()
m = int(input())
a = sorted(list(map(int, input().split())))

# 构造 01 段列表
sections = []
start = 0
for i in range(n):
    if s[i] == '0':
        sections.append((start, i))
        start = i + 1
sections.append((start, n))

# 统计未覆盖的 01 段个数
uncovered = len(sections) - 2

# 标记每个 01 段是否已被覆盖
flags = [False] * (len(sections) - 1)

# 循环直到所有的 01 段都被覆盖
ans = 0
while uncovered > 0:
    # 枚举每个未覆盖的 01 段
    for i in range(len(sections) - 1):
        if not flags[i]:
            left, right = sections[i]
            # 查找距离该 01 段最近的满足要求的左侧 01 段
            lidx = bisect_left(sections, (0, left))
            while lidx >= 0 and flags[lidx]:
                lidx -= 1
            lidx += 1
            # 查找距离该 01 段最近的满足要求的右侧 01 段
            ridx = bisect_right(sections, (right, n))
            while ridx < len(sections) - 1 and flags[ridx]:
                ridx += 1

            if lidx < i and ridx > i:
                flags[i] = True
                uncovered -= 1
                continue

            # 如果没有满足要求的 01 段,则需要进行修改
            idx = closest_idx(left, right, a)
            ans += 1
            sections.insert(i + 1, (a[idx] - 1, a[idx] - 1))
            flags.insert(i + 1, False)
            a.pop(idx)

print(ans)

参考文献

  1. P4233 【门|门CS 2013】