CCF 202109-2 非零段划分 python 满分

56 篇文章 4 订阅

题目叙述

问题描述:略

输入格式:略

输出格式:略

样例

样例1输入
11
3 1 2 0 0 2 0 4 5 0 2
样例1输出
5

样例2输入
14
5 1 20 10 10 10 10 15 10 20 1 5 10 15
样例2输出
4

样例3输入
3
1 0 0
样例3输出
1

样例4输入
3
0 0 0
样例4输出
0

满分证明

红色框框是我的,第一个是用网上讲用查分和后缀和写的(链接见参考)
在这里插入图片描述

解题思路

这题可以说是两个难点也可以说是一个,因为如果用差分法+后缀和的话就不需要计算给定列表切分后非零段了(虽然我也没太搞懂怎么利用了差分法)。

两个难点

第一,计算出给定列表切分后非零段

写出计算切分列表非零段数并不算太难,考察的是细心程度,按部就班来比自己分类讨论更好(我当时分类讨论漏了一种情况,导致连70分都没拿到 o(╥﹏╥)o)

这里的处理思路需要注意,最前面和最后面都加上0,方便处理索引问题
判断非零段时,需要判定前面为零且后面不为零,相关代码如下:

def count_z(l):
    flag = True  #这里相当于默认序列第0个前面数是0
    f_c = 0
    for i in l:
        if i != 0 and flag:
            f_c = f_c + 1
        if i != 0:
            flag = False
        else:
            flag = True
    return f_c

第二,进行优化(索引法)

如果写出这个再配合暴力求解就可以拿到70分,70分代码见下面
对于优化,**参考博文1**视频UP主讲,一是以空间换时间,利用已经存储的相关信息减少重复处理;二是对处理逻辑优化。

一共两层循环,外层循环已经是用了set()并排序,没办法在优化了。所以就要看一下内层可不可以优化,通过仔细观察(哈哈哈,这需要一定功力),我们可以发现实际上我们每次依据外层循环逐个计算非零段时,只与当前数值有关;又因为我们之前进行过排序(从小到大),所以我们尝试是否可以用上之前已经处理过的信息;
而且当前数值变为0后,如果两侧数据均大于0,则非零段数在之前基础上加一;如果两侧数据均等于0,则非零段数在之前基础上减一;否则不变(这才是优化的关键)
这里最重要的就是,我们可以先遍历一遍,把每个数所在的序号给存储下来,在进行遍历的时候直接修改列表值就可以了。举个栗子:

5 1 20 10 10 10 10 15 10 20 1 5 10 15

在去重,前后加0后变为:

0 5 1 20 10 15 10 20 1 5 10 15 0

使用字典嵌套列表存储为:

0:[0, 12]
5:[1, 9]
1:[2, 8]
20:[3, 7]
10:[4, 6, 10]
15:[5, 11]

按照排序后列表置零,如1;处理后为:

14
0 5 0 20 10 15 10 20 0 5 10 15 0

之前为1,依据上面判断法则,两个修改0处两侧均大于零,则加二;现在非零段为3;以此类推,下面不再重复赘述。

差分+后缀和实现思路

我是看了**参考博文2和**参考博文3才懂得,具体详细差分解释看了**参考博文4;但还是一知半解,我尝试给出自己理解;如果想更好理解请看上面链接,参考博文2还给出了其他解法(三分法、索引法、扫描线法和单调判定法)。

差分法非常巧妙,不需要计算某个数值的下的非零段数;假设P足够大,非零段数为0;事先存储一个数组表示P为某位数时非零段数变化;最后用后缀和依次判定,因为是从P最大开始,所以后缀和也是从最大数开始。
存储变化时,判定条件与我自己做优化时相似,不过他是不做置零处理,依次判定条件变为:
该数值两侧数据均大于该数值,则非零段数在之前基础上加一;如果两侧数据均小于他,则非零段数在之前基础上减一;否则不变(这是关键)

其他预处理过程与索引法相同(去除相邻相同重复,排序等),这里直接贴出大佬有详细注解的代码。

暴力求解70分代码

import copy

n = int(input())
ori_li = list(map(int, input().split()))

# 计算给定列表的非零段数
def count_z(l):
    flag = True
    f_c = 0
    for i in l:
        if i != 0 and flag:
            f_c = f_c + 1
        if i != 0:
            flag = False
        else:
            flag = True
    return f_c

li = [0]  # 最前面插入0
# 消除相邻重复数
for a in ori_li:
    if a != li[-1]:
        li.append(a)

li.append(0)  # 最后面插入0

n_z = count_z(li)  # 计算出原始非零段数
max_n_z = n_z

lo = list(set(li))
if 0 in lo:  # 防止只有0导致出错
    lo.remove(0)
lo.sort()

for a in lo:
    le = copy.deepcopy(li)
    for i in range(len(le)):
        le[i] = le[i] // a
    t_n_z = count_z(le)
    if t_n_z > max_n_z:
        max_n_z = t_n_z

print(max_n_z)


优化后满分代码

n = int(input())
ori_li = list(map(int, input().split()))


# 计算给定列表的非零段数
def count_z(l):
    flag = True
    f_c = 0
    for i in l:
        if i != 0 and flag:
            f_c = f_c + 1
        if i != 0:
            flag = False
        else:
            flag = True
    return f_c


li = [0]  # 最前面插入0
# 消除相邻重复数
for a in ori_li:
    if a != li[-1]:
        li.append(a)

li.append(0)  # 最后面插入0

n_z = count_z(li)
max_n_z = n_z
d = {}

for i, n in enumerate(li):
    if n not in d:
        d.setdefault(n, [i])
    else:
        d[n].append(i)

lo = list(set(li))
if 0 in lo:
    lo.remove(0)
lo.sort()

for a in lo:
    t_z = n_z
    for b in d[a]:
        li[b] = 0
    for b in d[a]:
        if li[b - 1] > 0 and li[b + 1] > 0:
            t_z = t_z + 1
        elif li[b - 1] == 0 and li[b + 1] == 0:
            t_z = t_z - 1
    n_z = t_z
    if t_z > max_n_z:
        max_n_z = t_z

print(max_n_z)

差分法+后缀和满分代码

n = int(input())
a = list(map(int, input().split()))
b = []
b.append(a[0])
i = 0
for i in range(1, n):  # 消除相邻重复的数
    if a[i - 1] != a[i]:
        b.append((a[i]))
x = max(b)  # 用x来存储列表b中的最大的数
c = [0 for i in range(10001)]  # c 的索引就表示 水位下降到这个索引时 非零段(山峰)是增加1 还是减少了1

b.insert(0, 0)  # 在第一个索引之前加入一个0  方便去比较
b.append(0)  # 在最后加入0

for i in range(1, len(b) - 1):
    if b[i - 1] < b[i] and b[i] > b[i + 1]:  # 若比相邻的俩个数都大,就说明下降到这个水位后 非零段增加1
        c[b[i]] += 1
    elif b[i - 1] > b[i] and b[i] < b[i + 1]:  # 若小就会把旁边的俩个非零段连接起来,非零段减少1
        c[b[i]] -= 1
max_c = 0
sum_c = 0  # 后缀和
for i in range(x, -1, -1):  # 因为水位是从最高的开始下降,所以需要后缀和,来判断下降到某一个水位时是增加还是减少。
    sum_c += c[i]
    if max_c < sum_c:  # 用max来记录最大的非零段
        max_c = sum_c
print(max_c)

感谢及参考博文

部分内容参考以下链接,这里表示感谢 Thanks♪(・ω・)ノ
参考博文1 202109(第23次认证)CCF CSP真题202109-1,2讲解
https://www.bilibili.com/video/BV1sP4y187Te
参考博文2 CCF202109-2 非零段划分(100分)【序列处理】
https://tigerisland.blog.csdn.net/article/details/120598581
参考博文3 csp 202109-2 非零段划分(python)
https://blog.csdn.net/qq_44240537/article/details/121664678
参考博文4 CCF202109-2非零段划分【差分法&&离散法】
https://blog.csdn.net/weixin_46134673/article/details/121451229

需者自取传送门(∩ᄑ_ᄑ)⊃━☆【CCF 2013-2021】本博主整理历年至少前两题 python 满分代码目录

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值