当前位置: 首页 > news >正文

牛客竞赛记录——小紫的优势博弈(Python3题解)

题目来源

链接:https://ac.nowcoder.com/acm/contest/103948/D
来源:牛客网

题目描述

定义一个字符串是“双生串”,当且仅当字符串中每一种字符出现的次数都是偶数次。

现在,小紫拿到了一个长度为 n n n 1 ≤ n ≤ 1 0 6 1\leq n\leq10^6 1n106),仅由字符‘0’和‘1’组成的字符串 s s s,她准备和小红玩一个游戏:

  • 小红先手操作,删除该串的一个非空前缀;
  • 小紫紧接着操作,删除该串的一个后缀(可以是空串)。

如果最终可以生成一个非空双生串,那么小紫将获得最终的胜利。

小紫发现这个游戏自己非常劣势,因为小红可以删除到只剩下一个字符导致自己必输,于是她强制让小红随机删除一个前缀(即删除 1 1 1 n n n 个字符)。请你计算小紫最终获胜的概率。

输入描述

第一行输入一个正整数 n n n 1 ≤ n ≤ 1 0 6 1\leq n\leq10^6 1n106) 代表字符串的长度。
第二行输入一个长度为 n n n,由字符‘0’和‘1’组成的字符串 s s s,代表初始字符串。

输出描述

在一行上输出一个实数,代表最终小紫获胜的概率。

由于实数的计算存在误差,当误差的量级不超过 1 0 − 6 10^{-6} 106 时,您的答案都将被接受。具体来说,设您的答案为 a a a ,标准答案为 b b b ,当且仅当 ∣ a − b ∣ max ⁡ ( 1 , ∣ b ∣ ) ≤ 1 0 − 6 \frac{|a - b|}{\max(1,|b|)}\leq10^{-6} max(1,b)ab106 时,您的答案将被接受。

示例1

输入

5
10010

输出

0.2

说明

在这个样例中,小红一共有五种删除前缀的方式:

  • 删除前一个字符,得到 “0010”;此时,小紫只需要删除最后的两个字符,得到 “00”,此时字符串是双生串,小紫可以获胜;
  • 删除前两个字符,得到 “010”;
  • 删除前三个字符,得到 “10”;
  • 删除前四个字符,得到 “0”;
  • 删除前五个字符,得到 “”;

其中,对于后四种情况,小紫无论怎么删除后缀,都无法得到双生串,所以,小紫获胜的概率为 1 5 = 0.2 \frac{1}{5}=0.2 51=0.2

题解

我这里准备提供两个题解,一个比较正规的,一个比较神秘的。(笑~~)
答案不是原创的,但是原创不好找,通过的代码里好多是一样的。

题解一:

来自:不玩原神也能学算法吗QAQ

def main():
    import sys
    input_data = sys.stdin.read().split()
    if not input_data: 
        return
    n = int(input_data[0])
    s = input_data[1].strip()
    
    # 状态用整数 0~3 表示,编码方式:state = p0*2 + p1,其中 p0, p1 分别表示'0'和'1'出现次数的奇偶性
    P = [0]*(n+1)
    # 初始状态为 0 (对应 (0,0))
    # 变量 p0, p1 表示当前奇偶性
    p0, p1 = 0, 0
    for i in range(n):
        if s[i] == '0':
            p0 ^= 1  # 异或1即取反
        else:  # s[i]=='1'
            p1 ^= 1
        P[i+1] = p0*2 + p1
    
    # 记录每个状态最后出现的位置
    last_occ = [-1]*4
    for i in range(n+1):
        last_occ[P[i]] = i

    # 对于每个可能的小红操作(删除前缀长度 i,i=1..n-1,因为i=n时剩空串),判断是否存在 j>i 使得 P[j]==P[i]
    win_count = 0
    for i in range(1, n):
        if last_occ[P[i]] > i:
            win_count += 1

    # 小红有 n 种选择(包括删除整个串的情况),但删除后空串无法构成胜利,所以分母为 n
    prob = win_count / n
    # 输出概率,满足误差要求
    print(prob)

if __name__ == '__main__':
    main()

这个解法比较正规,他在第一次遍历输入数组的时候,巧妙的用4个二进制数,(就是他的编码)来记录0和1的奇偶性。

之后遍历小红随机删除的所有可能,只要当前这个位置状态不在最后位置,就是可以得到双生子串的。
这里可能有人不明白,那么简单解释一下:奇数状态的两倍 = 偶, 偶数状态的两倍 = 偶。既然当前状态不在最后位置,那我把这个状态最后位置以后的删除了,不就得到两倍的当前状态吗?

什么,你问最后4个状态里有偶数怎么办?
那可以再加个判断呗。可惜的是这种题数据都比较多,你最后剩下那两种偶数状态,算误差里面去了。

  • 时间复杂度

    • 第一个循环用于计算状态编码,遍历字符串一次,时间复杂度为 O ( n ) O(n) O(n)
    • 第二个循环用于记录每个状态的最后出现位置,同样遍历一次,时间复杂度为 O ( n ) O(n) O(n)
    • 第三个循环用于计算小紫获胜的情况数,遍历一次,时间复杂度为 O ( n ) O(n) O(n)
    • 总体时间复杂度为 O ( n ) O(n) O(n),因为代码中没有嵌套循环,主要操作都是线性的。
  • 空间复杂度

    • P 列表长度为 n + 1last_occ 列表长度为 4,其余变量为常数级空间。
    • 因此,空间复杂度为 O ( n ) O(n) O(n),主要由 P 列表占用的空间决定。

解法二:神秘代码

n = int(input())
s = input()
k = 0
for i in range(n):
    a, b = 0, 0
    for j in range(i + 1, n):
        if s[j] == "0":
            a += 1
        else:
            b += 1
        if (a % 2 == 0 and b % 2 == 0):
            k += 1
            break
ans = k / n
print(ans)

这个解法就比较简单直白了。

  1. 输入读取

    • n = int(input()):从用户输入中读取一个整数 n,该整数代表字符串的长度。
    • s = input():读取一个长度为 n 的字符串 s,该字符串仅由字符 '0''1' 组成。
  2. 初始化计数器

    • k = 0:初始化一个计数器 k,用于记录满足特定条件的子串的数量。
  3. 嵌套循环遍历字符串

    • 外层循环 for i in range(n):遍历字符串 s 的每个位置 i,作为子串的起始位置。
    • 对于每个起始位置 i,内层循环 for j in range(i + 1, n)i + 1 开始遍历到字符串末尾,构建以 i 为起始位置的子串。
    • 在内层循环中,使用 ab 分别记录子串中字符 '0''1' 的出现次数。
    • 每次内层循环更新 ab 后,检查 ab 是否都为偶数。如果是,则将计数器 k 加 1,并使用 break 语句跳出内层循环,不再继续检查以当前起始位置 i 开始的更长子串。
  4. 计算概率并输出结果

    • ans = k / n:将满足条件的子串数量 k 除以字符串的长度 n,得到概率。
    • print(ans):输出计算得到的概率。

复杂度分析

  • 时间复杂度

    • 外层循环遍历字符串的每个位置,循环次数为 n
    • 对于每个外层循环的位置 i,内层循环从 i + 1 开始遍历到字符串末尾,内层循环的平均次数约为 n/2
    • 因此,总的时间复杂度为 O ( n 2 ) O(n^2) O(n2),因为嵌套循环的总执行次数近似为 ∑ i = 0 n − 1 ( n − i − 1 ) ≈ n ( n − 1 ) 2 \sum_{i = 0}^{n - 1} (n - i - 1) \approx \frac{n(n - 1)}{2} i=0n1(ni1)2n(n1)
  • 空间复杂度

    • 代码中只使用了常数级的额外变量,如 nskab,不随输入字符串长度的增加而增加。
    • 因此,空间复杂度为 O ( 1 ) O(1) O(1),表示只使用了常数级的额外空间。

为什么说它比较神秘呢?
哎,因为这个代码不稳定。不是说代码本身有什么问题,运行会出错什么的,而是说在牛客网上不稳定。你在提交的时候,可以会告诉你85%或者90%用例通过,运行超时,请检查什么什么的。但是有的时候却能通过。

所以,这个故事告诉我们,问了问题可以多找找别人的问题(坏笑)
好吧,开个玩笑,这个可以告诉我们下次参加竞赛的时候,如果告诉你运行超时,你可以多提交几次,说不定就过了。

相关文章:

  • CentOS 7 更换 YUM 源为国内
  • 高中信息技术学科核心素养评价体系的构建与实践研究
  • RESTful API 简介与使用
  • 数据结构——排序
  • 【探寻C++之旅】第九章:二叉搜索树
  • GetX 中GetView、GetXController 和 Bindings的联合使用
  • minikube部署Go应用
  • 蓝桥杯备考-----》差分数组+二分答案 借教室
  • deepseek连续对话与API调用机制
  • axios防止重复请求
  • DJ串烧集 2.4.5 | 海量大型DJ串烧歌曲,无广告,无需登录,高清在线播放
  • Apache Shiro 使用教程
  • Redis,从数据结构到集群的知识总结
  • OpenGL ES 入门指南:从基础到实战
  • 【JavaEE】Spring Boot 日志
  • 基于VMware的虚拟机集群搭建
  • 机器学习之浅层神经网络
  • Matlab 舰载机自动着舰控制系统研究
  • 咪咕MG101_晨星MSO9380芯片_安卓5.1.1_免拆卡刷固件包
  • Markdown 模板变量的使用
  • 上海交大曾小勤:科技传播不应停留于知识搬运,要做科学思维的播种机
  • 全国多家健身房女性月卡延长,补足因月经期耽误的健身时间
  • 外企聊营商|上海仲裁:化解跨国企业纠纷的“上海路径”
  • 词条数量大幅扩充,《辞海》第八版启动编纂
  • 浙江演艺集团7部作品组团来沪,今夏开启首届上海演出季
  • 上海国际珠宝时尚功能区未来三年如何建设?六大行动将开展