A1044:
题目链接
稍微提一下:当该题有与m相等的解时,按升序输出,且前面的数字小于后面的数字;若无相等的解,此时再输出最接近m的解
思路:
此题使用二分法,但题目给的数据却毫无顺序,同时输出答案为题目给出数据的顺序,因此也不可对原数据进行排序,所以如何使用二分法呢?
此题由于是子序和,而且所有数字都为正整数,我们不妨考虑按子序和保存在sum[]数组中,而sum数组显然是递增的序列,达到使用二分法的前提条件。
又可推导子序和的求法:sum[j] - sum[i - 1] = m(j > i),其差值diff与m进行比较,可作为二分法的查找关键字key
二分思路:
二分目的:找到第一个大于key值得元素,返回其数组下标
二分取值:sum数组的下标
二分判断:sum数组的元素与key值是否相等
个人解题时的思路
参考书上的sum数组的方法
查找相等的方案:由sum[i] - m = sum[pos],使用二分查找,在sum中是否存在这样的sum[pos]满足上式
查找不等的方案(最接近的值):因为不存在这样的sum[pos],应找出第一个小于sum[pos]的值
个人思路存在的问题
1、由于sum是递增的,按理说应该是找到最后一个满足sum[pos]的值,甚至在想可以先找出第一个不满足条件的pos,但下面要讲此思路仍然存在的问题
2、随后发现,要保留与m的差值,只有满足该差值的方才输出
3、慢慢意识到,一开始得到的差值并非是最佳的,需要找到最佳的差值,再进行遍历比对。我个人一开始便认为,由于序列递增,第一个大于sum[i]-m便应该是最合适,最接近的,其实并非如此,后面可能有更好的数据(随后我会提供一些样例)
启发
1、题目中涉及最值问题时,不建议遇到一个满足条件的便输出一个,应先找出最值,然后遍历比对,方才输出
2、二分法前提是有序,一定要找出有序数列!
几个测试样例
2 10
12 14
ans:1-1
6 13
2 3 4 5 7 9
ans:1-4
4 10
8 1 12 11
ans:4-4
代码
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3fffffff;
int n, m, diff = INF;
int sum[100005];
//查找第一个大于key的值
int upperbound(int left, int right, int key)
{
int mid;
while(left < right)
{
mid = (left + right) / 2;
if(sum[mid] > key)
{
right = mid;
}
else
{
left = mid + 1;
}
}
return left;
}
int main(int argc, char *argv[]) {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
{
scanf("%d", &sum[i]);
sum[i] += sum[i - 1];
}
for(int i = 1; i <= n; i++)
{
int pos = upperbound(i, n + 1, sum[i - 1] + m);//pos为第一个大于sum[i] + m的下标
if(sum[pos - 1] - sum[i - 1] == m)//sum[pos]大于sum[i] + m
{//此时验证sum[pos - 1] 是否等于 sum[i] + m
diff = m;
break;
}
else if(pos <= n && sum[pos] - sum[i - 1] < diff)//若无刚好相等的,则取最小值
{
diff = sum[pos] - sum[i - 1];
}
}
for(int i = 1; i <= n; i++)
{
int pos = upperbound(i, n + 1, sum[i - 1] + diff);
if(sum[pos - 1] - sum[i - 1] == diff)
{
printf("%d-%d\n", i, pos - 1);
}
}
return 0;
}