题目
OJ
思路
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)
总结
- 二分法还是有点小坑的,下次应用一定注意解不在区间内的情况。
- 不仅要注意时间复杂度还要注意空间复杂度。比如这题。
完结撒花(❁´◡`❁)