题目
题解
二分。
整体思路:计算累加和,枚举区间左端点,二分满足要求的区间右端点,记录左右端点。
满足的条件,找到的右端点和左端点之间的和大于等于pay。但是我们枚举每个左端点二分到区间和可能不正好是pay,所以我们将差值作为键,以vector作为值保存全部差值为key的区间信息。
产生了另一个问题,我们如何二分得到右端点?因为我们只能计算出前缀和,并不能直接计算出区间和,既然要求[l,r]
区间和大于等于pay,那么我们只要要求[1,r]
区间和大于pay+sum[l-1]
即可,sum[i]
表示前缀和。解决了!
虽然很快写出来了,但是感觉还挺有意思的,记录一下。
一开始不知道累加和如何转换到一段区间的和,想保存在sum[l][r]
中,但是这样时间复杂度就又到
n
2
n^2
n2了,所以想自己手写二分来判断,后来灵光一现,可以直接进行上面的处理达到目的!
代码
#include<bits/stdc++.h>
using namespace std;
#define PII pair <int, int>
const int N = 1e5+10;
int n, pay, x, a[N], mindiff = 0x3f3f3f3f;
map <int, set<PII> > diff; // key:表示多出来的钱数,value表示一个set,保存左右端点(其实vector也可以,因为我们插入的顺序就是按题目要求的)
int main()
{
cin >> n >> pay;
for (int i = 1;i <= n;i ++) cin >> x, a[i] = a[i-1] + x; // 计算前缀和
for (int i = 1;i <= n;i ++) {
int sum = pay + a[i-1]; // 右端点不能仅仅到达pay了,因为是累加和,所以要保证i~p到达pay,就需要保证1~p到达pay+a[i-1]
int p = lower_bound (a+i, a+n+1, sum) - a; // 二分得到右端点
if (p > n) continue;
int d = a[p] - sum; // 多出来的钱
diff[d].insert ({i, p}); // 保存左右端点
mindiff = min (mindiff, d); // 最小差值
}
for (PII item : diff[mindiff])
cout << item.first << '-' << item.second << endl;
return 0;
}