题目描述
难度分:2000
输入T(≤104)表示T组数据。所有数据的n之和≤2×105。
每组数据输入n(1≤n≤2×105)和长为n的数组a(1≤a[i]≤109)。
你需要把一段非空连续子数组替换成这段子数组的元素之积,替换后,需要让a的元素和尽量大。
例如a=[5,4,3,2,1],选择其中的[4,3,2],替换后a=[5,24,1]。
该替换操作必须恰好执行一次。
输出被替换的子数组的左右端点(下标从1开始)。如果有多种方案,输出任意一种均可。
输入样例
9
4
1 3 1 3
4
1 1 2 3
5
1 1 1 1 1
5
10 1 10 1 10
1
1
2
2 2
3
2 1 2
4
2 1 1 3
6
2 1 2 1 1 3
输出样例
2 4
3 4
1 1
1 5
1 1
1 2
2 2
4 4
1 6
算法
贪心+有技巧地枚举
这个题看着就感觉有数学规律在里面,因为随着替换的子数组长度越来越大,很可能这个乘积是非常大的,写高精度肯定要超时。
我们可以发现一点,在一般情况下,被替换的子数组端点a[l]和a[r]肯定是两个大于1的数,因为再往两边囊括1是没有意义的,1不会增大乘积但是会减少和。这样一来搜索空间就减小很多了,我们可以先将满足a[i]>1的索引i加入到一个数组pos中。但考虑到pos可能长度很大,也不能直接O(n2)枚举端点l和r。
还可以发现一点,由于218>n,所以如果a中大于1的a[i]不小于18个,那一定是把子数组[pos[0],pos.back()]替换掉最好,因为这些大于1的数相乘最少都有218,不替换肯定不如替换为乘积后得到的数组和。因此,分为以下两种情况:
- 如果pos.size()≥18,答案就是(pos[0],pos.back())。
- 否则都少于18个候选了,直接双重循环枚举l和r,大数计算收益的增量(用
python
可以避免写高精度),找到增量最大时对应的l和r输出即可。
复杂度分析
时间复杂度
遍历一遍a数组得到所有满足a[i]>1的索引pos,时间复杂度为O(n)。接下来双重循环枚举(最大常数操作为182次)+大数连乘的时间复杂度是很低的(数位最多不会超过200)。因此,算法的瓶颈还是在于遍历a数组,整体时间复杂度为O(n)。
空间复杂度
除了输入的数组a,空间瓶颈在于pos和a的前缀和数组s(主要是为了快速计算区间和,从而得到收益的增量),它们都是线性的空间消耗。因此,整个算法的额外空间复杂度为O(n)。
python 代码
from itertools import accumulate
t = int(input())
for _ in range(t):
n = int(input())
a = [0] + list(map(int, input().split()))
s = list(accumulate(a))
tot = s[-1]
window_sum = lambda l, r: s[r] - s[l - 1]
pos = []
for i in range(1, n + 1):
if a[i] > 1:
pos.append(i)
if len(pos) >= 18:
print(pos[0], pos[-1])
else:
sz = len(pos)
left, right = 1, 1
mx = tot
for l in range(sz):
prod = 1
for r in range(l, sz):
sumation = window_sum(pos[l], pos[r])
prod *= a[pos[r]]
if tot + prod - sumation > mx:
mx = tot + prod - sumation
left, right = pos[l], pos[r]
print(left, right)