和与乘积-蓝桥杯国赛真题

题目

在这里插入图片描述

OJ

https://www.lanqiao.cn/problems/1595/learning/?page=1&first_category_id=1&sort=students_count&name=%E5%92%8C%E4%B8%8E%E4%B9%98%E7%A7%AF

思路

1.暴力法

既然有前缀和那么同理可以有前缀乘。直接使用前缀乘和前缀和判断,理论上复杂度可以降低到o( n 2 n^2 n2),很遗憾这里行不通,因为元素的大小最大为200000,极端条件下前缀乘可以呈指数爆炸式增长,而空间有限,所以会有内存不够用,引发段错误。

n = int(input())
a = [0]+list(map(int,input().split()))
asum = [0]*(n+1)
amul = [1]*(n+1)
for i in range(1,n+1):
    asum[i] = asum[i-1]+a[i]
    amul[i] = amul[i-1]*a[i]
##print(asum)
##print(amul)
ans = 0
def check(c,d):
    if asum[d]-asum[c-1] == amul[d]/amul[c-1]:
        return True
    return False
for i in range(1,n+1):
    for j in range(i,n+1):
        if check(i,j):
            ans += 1
print(ans)

2.遍历所有元素

首先,明确一件事,对于长度为n的数组,那么保底有n个答案(每个元素单独抽出来)。考虑一下区间[i,j]是否是一个答案,j可以从i+1出发一直遍历到n,一开始可以视作和与积已经相等(看作是从[i,i]出发一直到[i,n])。有一句话说的好,揉面的诀窍:“水多了加面,面多了加水。”
我们可以观察到,当加入元素1时,和s加一,积p不变,而加入元素大于1时,积p的增加量大于和s的增加量。
同时可以观察到元素最大为200000,n最大为200000,那么一个可能解内大于1的数最多为36个。
在这里插入图片描述
也就是说可以从j可以从i往后找36个大于1的数,然后判断是否能配凑出一个解。
假设bsm[k]为第k个元素后面有bsm[k]个1。那么只要前缀和p和前缀乘s满足:s <= p <= s+bsm[k],那么这里可以找出一个解。
对于如何找出i后面大于1的元素可以使用二分法遍历大于1元素的下标。构造前缀和、探测后面有几个1、记录大于1元素的个数以及下标可以放在一个遍历里完成。

n = int(input())
## a 原数组; suma 前缀和; biga 前缀大于1元素数; 
## bsma 后面有几个连续的1; bindex 大于1元素的下标; bn 大于1元素的数量;
a = [0]+list(map(int,input().split()))
suma = [0]*(n+1)
biga = [0]*(n+1)
bsma = [0]*(n+1)
bindex = []
bn = 0
ans = n
for i in range(1,n+1):
    suma[i] = suma[i-1]+a[i]
    if a[i] > 1:
        biga[i] = biga[i-1]+1
        bn += 1
        bindex.append(i)
    else:
        biga[i] = biga[i-1]
temp = 0
for i in range(n,0,-1):
    bsma[i] = temp
    if a[i] > 1:
        temp = 0
    else:
        temp += 1
for i in range(1,n):
    l = 0
    r = bn-1
    now = a[i]
    while l < r:
        mid = int((l+r)/2)
        if bindex[mid] > i:
            r = mid
        else:
            l = mid+1
    if bindex[l] <= i:
        ## 这里为了判断是否还在合法的区间,因为二分法如果解根本不在区间内,也会有一个最接近的解。
        ## 如果已经超出了,那么意味着后面全是1,不需要考虑了
        break
    for j in range(l,bn):
        if biga[bindex[j]]-biga[i-1] > 36:
            break
        now *= a[bindex[j]]
        sumof = suma[bindex[j]]-suma[i-1]
        if now <= sumof+bsma[bindex[j]] and now >= sumof:
            ans += 1
print(ans)

3.遍历所有大于1的元素

对于思路2,你是否感觉到做了很多重复的工作?
比如,计算前缀乘,只需要考虑到大于1的元素。而前缀i从1遍历到n-1的过程中算了很多遍。是否可以考虑直接遍历大于1的元素然后进行配凑?
当然可以。选定一个区间,可以递归的求出前缀乘,然后判断这段区间前后1的个数,再判断能构造出几个解。
这段代码不是我写的,但是我觉得这个思路和代码很棒,就也写在这里

sum = [0]
s = lambda l,r:sum[r]-sum[l-1]
index = [0]
def count(left,right,delt):
    min,max = left,right
    if min>max:
        min,max = max,min
    total = min+max
    if delt<0 or delt>total:
        return 0
    if delt<=min:
        return delt+1
    if delt>=max:
        return total-delt+1

    return min+1 
if __name__=='__main__':
    n = int(input())
    a = [None]+[int(x) for x in input().split()]

    for i in range(1,n+1):
        sum.append(sum[-1]+a[i])

        if a[i]>1:
            index.append(i)

    index.append(n+1)

    up = len(index)-1

    ans = n
    
    for i in range(1,up):
        last_l = index[i-1]
        l = index[i]

        p = a[l]

        upper = min(up,i+35)
        
        for j in range(i+1,upper):
            r = index[j]
            r_next = index[j+1]
            p*=a[r]
            delt = p-s(l,r)

            left = l-last_l-1
            right = r_next - r -1
            ans +=count(left,right,delt)
    print(ans)

总结

  • 二分法还是有点小坑的,下次应用一定注意解不在区间内的情况。
  • 不仅要注意时间复杂度还要注意空间复杂度。比如这题。

完结撒花(❁´◡`❁)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值