CCF 202109-2 非零段划分 python 满分
题目叙述
问题描述:略
输入格式:略
输出格式:略
样例
样例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.youkuaiyun.com/article/details/120598581
参考博文3 csp 202109-2 非零段划分(python)
https://blog.youkuaiyun.com/qq_44240537/article/details/121664678
参考博文4 CCF202109-2非零段划分【差分法&&离散法】
https://blog.youkuaiyun.com/weixin_46134673/article/details/121451229
需者自取传送门(∩ᄑ_ᄑ)⊃━☆【CCF 2013-2021】本博主整理历年至少前两题 python 满分代码目录