題目:click me~
題意:給出一個數字序列和一個數S,在數字序列中求出所有和值爲S的連續子序列,(區間下標左端點小的先輸出,左端點相同時右端點小的先輸出)。若沒有這樣的序列,求出和值大於S但最小的子序列。
解題思路:
令sum[i]表示A[1]到A[i]的和值,由於序列都是正值,因此sum[i]一定是嚴格單調遞增的,(初始化sum[0]=0)。這樣做的好處在於,若要求連續子序列A[i]到A[j]的和值,只需要計算sum[j]-sum[i-1]即可。
有了嚴格單調遞增的sum數組,那就可以用二分法來做這道題。枚舉左端點i(1<=i<=n),然後在sum數組(i,n)的範圍內查找值爲sum[i-1]+S的元素是否存在:如果存在,用對應的下標j做右端點;如果不存在,找到第一個使和值超過S的右端點j.(可以用upper_bound函數來寫)
考慮到題目要求輸出所有方案,因此需要對數組進行兩次遍歷,第一次遍歷求出大於等於S的值nearS,第二次遍歷求出和值爲S的方案並輸出。總複雜度爲O(nlogn)。
code
#include<iostream>
using namespace std;
const int N = 100010;
int sum[N];
int n, S, nearS = 100000010;
int upper_bound(int L, int R, int x) {
int left=L,right=R,mid;
while (left < right) {
mid = (left + right) / 2;
if (sum[mid] > x)right = mid;
else left = mid + 1;
}
return left;
}
int main() {
cin >> n >> S;
sum[0] = 0;
for (int i = 1;i <= n;i++) {
int a;
cin >> a;
sum[i] = sum[i - 1] + a;
}
for (int i = 1;i <= n;i++) {
int j = upper_bound(i, n + 1, sum[i - 1] + S);
if (sum[j - 1] - sum[i - 1] == S) {
nearS = S;
break;
}
else if (j <= n && sum[j] - sum[i - 1] < nearS)nearS = sum[j] - sum[i - 1];
}
for (int i = 1;i <= n;i++) {
int j = upper_bound(i, n + 1, sum[i - 1] + nearS);
if (sum[j - 1] - sum[i - 1] == nearS)
cout << i <<"-"<< j - 1<<endl;
}
return 0;
}