想要查看该套试卷的其它题解,请点击
题目描述
给定一个数列 A=(a1,a2,⋯,an),问有多少个区间 [L,R] 满足区间内元素的乘积等于他们的和,即⋅aL+1*aR=aL+aL+1+⋯+aR 。(就是区间积与区间和)
输入描述
输入第一行包含一个整数 n,表示数列的长度。
第二行包含 n 个整数,依次表示数列中的数 1,2,⋯,a1,a2,⋯,an。
输出描述
输出仅一行,包含一个整数表示满足如上条件的区间的个数。
输入输出样例
示例
输入
4
1 3 2 2
输出
6
样例解释
符合条件的区间为 [1,1],[1,3],[2,2],[3,3],[3,4],[4,4][1,1],[1,3],[2,2],[3,3],[3,4],[4,4]。
评测用例规模与约定
对于 20% 的评测用例,n≤3000n≤3000;
对于 50% 的评测用例,n≤20000n≤20000;
对于所有评测用例,1≤n≤200000,1≤ai≤200000。
运行限制
- 最大运行时间:3s
- 最大运行内存: 256M
思路:
这个题要求的是数组中 ‘区间和’与‘区间积’ 相等的数量,很明显是要用到前缀和的,对于积我们可以用相同的思路,只不过从 ‘加减’ 变成了 ‘乘除’
例子:1 2 5 3 4 的前缀积是:1 2 10 30 120 。要想O(1)的得到1~3的区间积就是arr[3]//arr[0]
然后我们很容易想到暴力的做法,两重循环外循环左边界,内循环右边界,这样时间复杂度是O(n^2)只能通过很少的案例
我们想一下,乘积是要比和涨的更快的,那么在内循环 右边界时 只要区间积大于区间和是不是就可以结束此次内循环了? 其实并不是的,因为有数字1的存在,在碰到数字1的时候 区间积是不涨的 而区间和是加一的
例子:2 3 2 1 1 1 1 1 ,前缀积:2 6 12 12 12 12 12 12,前缀和:2 5 7 8 9 10 11 12。
所以数字1是一个特殊的存在,如果没有1,当区间积大于区间和后确实可以结束内循环,而因为有了1,区间和就有可能追上区间积,所以我们在遍历的时候可以只遍历数字不为1的位置,且在区间和<区间积 的时候可以考虑两边的1是否可以补上差距
例子:1 1 1 3 4 1 1 1 1 5 6 ,我们在遍历的时候只遍历3 4 5 6,对于 3 4 ,他们的区间积>区间和,但我们可以通过两边的1补上差距
此时又有一个新的问题,对于上面的例子,两边的1是超过我们的需求的,也就是说他们可以提供多个区间,可以是1 1 3 4 1 1 1 或者1 1 1 3 4 1 1又或者1 3 4 1 1 1 1,而实际情况中两边的1可能大大的超出我们的需求,如果我们一个个的算,那时间复杂度又变的很高了,所以我们要O(1)的求出补上两边的1后我们可以得到几个区间。其实我们可以想象要补的1是一个滑动块,他们能滑动的空间就是我们能得到的区间数
例子:1 1 1 1 1 3 4 1 1 1,可以是1 1 1 1 1 3 4,1 1 1 1 3 4 1,1 1 1 3 4 1 1,1 1 3 4 1 1 1
需要注意的是对于有一边的1超过我们的需求的情况,我们要强制他不能超过我们的需求
例子: 1 1 1 1 1 1 1 1 1 3 4 1 1 1 1 1 1 1,将他限制为1 1 1 1 1 3 4 1 1 1 1 1,不然就有可能出现1 1 1 1 1 3 4 这种被补的1不靠近3 4 的情况
2023年5月16日:
经人指点后发现,我们可以将所有连续的非1数字看成一个非1区间,在我们比较区间和与区间积的时候,如果 区间积减区间和 已经无法通过左方补1来弥补了,那么有边界所在的非1区间则可以直接跳至该非1区间的末尾,因为这样,左右方就都有1了
例如:1,1,1,3,4,2,5,6,1,1,1,1,1,1,1,1,1,1,1,1。若刚开始左边界在数字3,而右边界在数字4,此时 区间积减区间和为 5 ,左方1已经不够用了,右边界就可以直接跳到数字6,这样就避免了右边界从4到6。输入案例中的非1区间越长,优势就越大。
2023年5月16日下午:
到此我们的做法仍然不能通过全部的测试用例,此时我们再观察一下样例范围,我们发现数组最大长度为:2e5 , 最大元素项为2e5,我们可以想到前缀和最大也就是200000*200000 也就是4e10,所以前缀积的极限就是4e10!!!
此时我们的代码终于通过了所有案例!!!
代码实现:
#区间最大长度2e5,最大元素2e5,区间和最大 2e5*2e5=4e10,,区间积不可超他
################初始化
n=int(input())+1
arr=[0]+[int(i) for i in input().split()]
sum=arr.copy()#将成为前缀和
pro=sum.copy()#将成为前缀积
pro[0]=1
lone=[0 for _ in range(n)]#左方1个数
rone=lone.copy()#右方1个数
index=[]#非1下标
total=n-1#最终相同区间个数,单个的先算上
l=0
for i in range(1,n):
sum[i]+=sum[i-1]
pro[i]*=pro[i-1]
if arr[i]==1:
l+=1
else:
lone[i]=l
l=0
index.append(i)
has=[None for _ in range(n+1)]#跳至当前非一段末尾
h=None#h是当前段末尾
r=0
for i in range(n-1,0,-1):
if arr[i]==1:
r+=1
h=None
else:
if h:
has[i]=h
else:
h=i
rone[i]=r
r=0
##########主体
for i in range(len(index)-1):
j=i+1
s = sum[index[j]] - sum[index[i] - 1] # 区间和
p = pro[index[j]] // pro[index[i] - 1] # 区间积
while p<4e10:
if s==p:
total+=1
elif s<p :
if (lone[index[i]]+rone[index[j]])>=(p-s):#补上两边的1可以合格
mend=(p - s)#差
l_gap = min(lone[index[i]], mend)#左1
r_gap = min(rone[index[j]], mend)#右1
gap=l_gap+r_gap
total+=gap-mend+1#补1的所有情况
elif has[j]:#补不够1且能跳
j=has[j]#跳至非1段末尾
continue
j+=1
if j==len(index):
break
s = sum[index[j]] - sum[index[i] - 1] # 区间和
p = pro[index[j]] // pro[index[i] - 1] # 区间积
print(total)