数列
提交文件:bound.exe
输入文件:bound.in
输出文件:bound.out
问题描述:
给定n(1<=n<=100000)个绝对值不超过10000的整数,在这个数列中找一个连续的非空子序列,使其和的绝对值距离整数t(0<=t<=1000000000)最近。
输入格式:
输入包含多组数据。
每组数据先输入n和k。n=k=0表示输入结束。
接下来n个用空格分隔的整数,表示数列中的每一个元素。
接下来k个用空格分隔的整数,每个整数表示一次询问的t值。
输出格式:
对于每组数据的每次询问,输出用空格分隔的三个整数:最接近t的连续子序列的和的绝对值,连续子序列的左边界,连续子序列的右边界。子序列可能从1到n。如果存在多个最接近的解,输出任意解都可行。
输入样例: | 输出样例: |
5 1 -10 -5 0 5 10 3 10 2 -9 8 -7 6 -5 4 -3 2 -1 0 5 11 15 2 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 15 100 0 0 | 5 4 4 5 2 8 9 1 1 15 1 15 15 1 15 |
这一题最朴素的方法是暴力枚举,时间复杂度为O(tn^2),显然会超时。
利用尺取法去做,可以把每个询问的时间复杂度由O(n)降到O(1)。能用尺取法的问题满足一定的单调性,我们后面再提。
对本题而言,尺取法是对于在求区间时,不一一枚举所有区间的和,而是定义l为区间左端点、r为区间右端点,扫描过程中持续推进左端点和右端点并更新记录最优解,直到r>n;
前面说到,可以用尺取法的题目必须满足一定的单调性;对比一下与二分的过程,可以发现,无论在二分还是尺取法中,单调性可以帮助我们判断什么时候推进l,什么时候推进r。
把前缀和从小到大排一下序,那么对于每段区间[l,r],若sum[r]-sum[l]<t,那么我们希望区间和更大以接近t;而因为前缀和已经从小到大排过一次序,r++,则sum[r]增大,区间和将更大;同理,sum[r]-sum[l]>t,我们希望区间和更小以接近t,l++,则sum[l]增大,区间和减小;而sum[r]-sum[l]==t时,此时必定最接近t了嘛(都相等了那当然是最接近的了!)。
但是,因为答案要求我们输出最接近t的区间和时对应的区间端点,那么在读入时可以对每个前缀和的下标记录一下,排序的时候也顺带交换即可(记得每组数据处理前要初始化数组)。
代码如下:
#include<cstdio>
#include<cstring>
using namespace std;
int a[100001];
int label[100001];
int n,m;
void qs(int l,int r)
{
int i=l;
int j=r;
int m=a[(l+r)/2];
while (i<=j)
{
while (a[i]<m) i++;
while (a[j]>m) j--;
if (i<=j)
{
int k=a[i];
a[i]=a[j];
a[j]=k;
k=label[i];
label[i]=label[j];
label[j]=k;
i++;
j--;
}
}
if (i<r) qs(i,r);
if (j>l) qs(l,j);
}
int abs(int x)
{
if (x<0) return (-x); else return x;
}
int main()
{
freopen("bound.in","r",stdin);
freopen("bound.out","w",stdout);
scanf("%d%d",&n,&m);
while (n!=0&&m!=0)
{
memset(a,0,sizeof(a));
label[0]=0;
for (int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
a[i]=a[i-1]+x;
label[i]=i; //记录下标
}
qs(0,n);
for (int i=1;i<=m;i++)
{
int t;
scanf("%d",&t);
int l=0;
int r=1;
int ans=2147483647;
int left_most;
int right_most;
while (r<=n) //尺取法过程
{
if (abs(a[r]-a[l]-t)<abs(ans-t))
{
ans=abs(a[r]-a[l]);
left_most=label[l];
right_most=label[r];
}
if (a[r]-a[l]<t) r++;
else if (a[r]-a[l]>t) l++;
else if (a[r]-a[l]==t) break;
if (l==r) r++;
}
if (left_most>right_most)
{
int k=left_most;
left_most=right_most;
right_most=k;
}
printf("%d %d %d\n",ans,left_most+1,right_most);
}
scanf("%d%d",&n,&m);
}
return 0;
}