首页 > 试题广场 >

小红的01子序列构造(easy)

[编程题]小红的01子序列构造(easy)
  • 热度指数:8227 时间限制:C/C++ 3秒,其他语言6秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
\hspace{15pt}给定一个仅由字符 \tt 0,1 组成的字符串 s,长度为 n。小红想找到一个闭区间 [l,r] 使得在子串 s_{l..r} 中,恰好存在 k 个严格等于 01子序列(即选取下标 i<j,满足 s_i=\texttt{0},\ s_j=\texttt{1})。
\hspace{15pt}请你输出任意一个满足条件的区间;若不存在,则输出 -1

【名词解释】
\hspace{15pt}子序列:从字符串中删除任意个(可为零)字符后得到的字符串,保留剩余字符原有相对顺序。

输入描述:
\hspace{15pt}第一行输入两个整数 n,k\left(1\leqq n\leqq 2\times10^{5};\ 1\leqq k\leqq 10^{10}\right)——字符串长度与目标子序列数量。 
\hspace{15pt}第二行输入一个长度为 n 的 01 串 s(下标从 1 开始)。


输出描述:
\hspace{15pt}若不存在满足要求的区间,输出单独一行-1;否则输出两个整数 l,r\left(1\leqq l\leqq r\leqq n\right) 表示区间端点(输出任意一组均可)。
示例1

输入

4 2
0011

输出

1 3

说明

子串 s_{1..3}=\texttt{ 内的 01 子序列共有 2 个:s_1s_3s_2s_3
示例2

输入

4 2
1110

输出

-1

注意到字串变长时,其中的 01 子序列数量单调不减,故可以用二分。

(注释由 AI 生成,但是 AI 太笨了不会讲原理,基本只是在解释每一步做了什么)

# 导入operator模块的le方法(注:当前代码中未实际使用该方法,属于冗余导入)
from operator import le
# 读取输入的n和k:n为字符串长度,k为目标匹配值
# strip()去除首尾空白,split()按空格分割,map(int, ...)转换为整数类型
n, k = map(int, input().strip().split())
# 读取输入的二进制字符串s(仅包含'0'和'1')
s = input().strip()
# 前缀数组:pre_0[i] 表示字符串s的前i个字符(s[0..i-1])中'0'的个数
# 长度为n+1,方便处理边界情况(前缀和常用初始化方式)
pre_0 = [0] * (n + 1) 
# 后缀数组:suf_1[j] 表示字符串s从j位置开始到末尾(s[j..n-1])中'1'的个数
suf_1 = [0] * n 
# 后缀数组:suf_01[j] 表示字符串s从j位置开始到末尾,每个'0'对应的后续'1'的个数之和
# 即对于s[j..n-1]中的每个'0',累加其右侧(包括自身之后)的'1'数量,最终求和
suf_01 = [0] * n 
# 初始化suf_1的最后一个元素(最右侧字符):如果是'1',则后缀1的个数为1
if s[-1] == '1':
    suf_1[-1] = 1
# 循环遍历,同时构建前缀数组pre_0和后缀数组suf_1、suf_01
for i in range(n):
    # 构建pre_0:继承前一个位置的前缀0个数(pre_0[i-1])
    # 注:i=0时,pre_0[i-1] = pre_0[-1],即pre_0数组的最后一个元素(初始为0)
    pre_0[i] = pre_0[i - 1]
    # 如果当前字符是'0',前缀0的个数加1
    if s[i] == '0':
        pre_0[i] += 1

    # 当i>0时,反向遍历构建后缀数组(j = n-1-i 从倒数第二个字符向前遍历)
    if i > 0:
        # 计算反向遍历的索引j(从后往前第i+1个位置)
        j = n - 1 - i
        # 构建suf_1:继承j+1位置的后缀1个数(右侧的后缀1总数)
        suf_1[j] = suf_1[j + 1]
        # 如果当前字符是'1',后缀1的个数加1
        if s[j] == '1':
            suf_1[j] += 1

        # 构建suf_01:继承j+1位置的suf_01值(右侧的0对应1的累计和)
        suf_01[j] = suf_01[j + 1]
        # 如果当前字符是'0',则累加当前位置右侧的1的个数(suf_1[j])到suf_01[j]
        if s[j] == '0':
            suf_01[j] += suf_1[j]
# 自定义二分查找函数:在区间[i, n-1]中查找符合target条件的最大索引j
# 参数i:查找的左边界起始位置;target:目标匹配值
def bisect(i, target):
    # 二分查找的左边界(起始为i)和右边界(末尾为n-1)
    left = i 
    right = n - 1
    # 二分查找循环:当左边界不超过右边界时继续
    while left <= right:
        # 计算中间索引mid(等价于(left + right) // 2,位运算更高效)
        mid = left + right >> 1
        # 计算决策值dec:用于判断是否满足目标条件
        # 组成:suf_01[mid](mid右侧0对应1的累计和) + 区间[i, mid-1]内0的个数 * suf_1[mid](mid右侧1的个数)
        dec = suf_01[mid] + (pre_0[mid - 1] - pre_0[i - 1]) * suf_1[mid]
        # 如果dec大于等于目标值,说明需要向右查找更大的索引
        if dec >= target:
            left = mid + 1
        # 否则向左查找
        else:
            right = mid - 1

    # 循环结束后,计算右边界right对应的dec值(验证是否符合目标)
    dec = suf_01[right] + (pre_0[right - 1] - pre_0[i - 1]) * suf_1[right]
    # 如果dec等于target,返回right(找到符合条件的索引);否则返回特殊值-114514(标记未找到)
    return right if dec == target else -114514 
# 核心求解函数:查找满足条件的字符串区间[i+1, j](题目要求输出1-based索引)
def solve():
    # 遍历每个可能的起始位置i(0-based,对应字符串s的第i个字符)
    for i in range(n):
        # 情况1:如果从i位置到末尾的suf_01值恰好等于k,直接返回区间[i+1, n](1-based)
        if suf_01[i] == k:
            return f'{i + 1} {n}'
        # 情况2:如果从i位置到末尾的suf_01值小于k,说明不存在符合条件的区间,返回'-1'
        if suf_01[i] < k:
            return '-1'
        # 情况3:计算目标值target = 当前suf_01[i] - k,用于二分查找
        target = suf_01[i] - k 
        # 调用二分查找函数,查找符合target条件的j
        j = bisect(i, target)
        # 如果j返回特殊值(未找到),继续遍历下一个i
        if j < 0:
            continue 
        # 找到符合条件的j,返回区间[i+1, j](1-based索引)
        return f'{i + 1} {j}'
# 调用求解函数并打印结果
print(solve())

# ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⠶⠟⠛⠛⠛⠛⠷⡶⠶⠿⠛⠷⠶⣦⣤⡀
# ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠶⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠶⣄
# ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡴⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢶⡀
# ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⠫⠒⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢄⠀⠀⠀⠀⠉⢷⡀
# ⠀⠀⠀⠀⠀⠀⣠⡾⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠒⡀⠀⠀⠀⠙⣦
# ⠀⠀⠀⠀⣠⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢆⠀⠀⠀⠘⣧
# ⠀⠀⠀⣴⠁⠀⢀⠤⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢣⠀⠀⠈⢿⡀
# ⠀⠀⡾⠀⣠⢾⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡆⠀⠀⠀⠹⣆
# ⠀⢸⢁⠞⠀⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⣿⠀⠀⠀⠀⠀⠀⢰⣿⠑⠢⣄⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠘⣆
# ⠀⠘⠃⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠠⠋⢀⢏⢿⠀⠀⠀⠀⠀⢀⣿⣌⣆⠀⠀⣏⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠈⠀⠀⢄⠀⠀⢹⡀
# ⠀⠀⠀⠀⢀⡇⠀⠀⠀⠀⠀⠀⢠⠀⠀⡟⠟⢞⣇⠀⠀⠀⠀⢿⠟⠀⠙⣆⠀⢹⢦⡀⠀⠘⠀⠀⠀⠀⠀⠀⣄⠀⠀⠀⢳⡀⠀⣇
# ⠀⠀⠀⣤⠋⠀⠀⠀⠀⠀⠀⢀⡿⡄⢸⠙⠀⠀⠻⣆⢠⣄⠀⠀⣧⠀⠀⠀⠛⢦⣷⠙⢶⣄⠀⠀⠀⠀⠀⠀⠹⡀⠀⢠⣼⠻⡀⡟
# ⢀⣴⠟⠀⠀⠀⣀⠞⠀⠀⠀⣼⢋⠈⠻⠀⢀⣪⡶⠀⠛⠃⠉⠛⠻⣬⣑⣀⣀⣀⣤⣶⣷⣸⠀⠀⠀⠀⠠⠀⠀⠙⣄⠀⣿⠀⠛
# ⠀⠉⠛⠛⡿⠁⠀⠀⣄⠀⠀⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⡿⠿⡿⠀⠀⠀⠀⠀⡾⠓⠶⠶⠋⠘⣦
# ⠀⠀⢀⠟⠀⠀⠀⣼⣿⣦⡀⠹⡀⠀⠙⠁⠀⠀⠒⠶⠒⠲⠤⠴⠀⠀⠀⠀⠁⠀⠀⠀⣾⠀⠀⠀⠀⠀⡾⠉⢷⣳⢄⠀⠀⠈⠳⣄
# ⠀⠀⡿⠀⠀⠀⣠⣿⣿⢿⣿⠉⠉⠎⢀⠀⡎⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠀⠃⢸⠀⣿⡤⣶⠁⠀⢠⣿⣿⠛⢀⣿⡇⡿⠶⣤⣤⣤⠟
# ⠀⠀⡇⠀⡴⢋⣼⠗⣉⢯⣿⣷⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡾⠀⣀⣾⠉⠉⠀⣠⣿⣿⣧⣿
# ⠀⠀⠹⠟⠀⣧⡴⣻⠃⣾⣿⣿⣋⠛⢶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣛⣿⣿⣍⣴⣭⣿⡻⣿⣿⣿⣿⣞⣦
# ⠀⠀⠀⠀⠀⣾⣿⠃⢰⣿⠟⠐⣿⢏⣿⣿⣝⣿⣿⣿⠿⠶⠶⠶⠶⠶⠶⣿⠟⣩⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⣿⣷⣿⣦⣤
# ⠀⠀⠀⠀⠀⠠⣧⡀⣿⠁⢀⠋⢠⠋⠀⠙⠀⠉⠁⠀⠀⠀⠀⠀⠀⣴⣻⣿⣿⠦⣠⠛⠋⠁⣿⣿⠋⠻⣿⣿⣿⠉⠛⠿⠿⠛⠋
# ⠀⠀⠀⠀⠀⠀⠀⠀⣿⣾⢻⢠⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⢸⣿⡿⣵⠛⠀⠀⠀⠀⠁⠀⠀⠀⣿⠟
# ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣏⡟⡟
#
发表于 2025-12-29 14:39:58 回复(0)