NOKJ 3545 接近
问题描述
对于一个数字序列A,并且有若干询问。对于每个询问,要求求出一段在序列A中非空 的连续段使得这一段数字的总和的绝对值尽量接近P。
输入格式
第一行2个数N、T,表示序列的长度和询问的个数。
接下来一行N个整数,表示A序列。 接下来T行,每行一个数P表示询问。
输出格式
共输出T行,每行对应一个询问的答案。
输出3个数:第一个数为能够实现的最接近P 的数,后面两个数L、R表示A序列中的L到 R这一段数能实现这个答案。
如果存在多解,输出L最小的解;
如果还有多解,输出R最小的解。
样例输入
输入样例1
5 1
-10 -5 0 5 10
3
样例输入2
6 2
-2 5 3 0 -3 -4
1
6
样例输入3
7 2
-2 -1 -2 4 1 -3 7
0
6
样例输出
样例输出1
5 2 2
样例输出2
1 1 6
6 1 3
样例输出3
0 1 5
6 2 7
数据范围
30%的数据 1<=N<=1,000。
60%的数据 1<=N<=10,000。
100%的数据 1<=N<=100,000,A 序列中数字绝对值<=10,000,T<=100,询问的 数字<=10^9
由于有多组询问,容易想到两种思路:
1.预处理出所有答案并排序,询问时二分查找;
2.对每次询问都跑一遍。
显然第一种思路是行不通的,预处理出所有区间前缀和之差就是 O(N2) 。那么只有考虑第二种思路。鉴于数据范围,只能承受时间复杂度 O(NT) 或者更快的算法。每次询问 O(N) ,那么容易想到单调队列。
然而这样就有一个问题:前缀和数组并不满足单调性,这怎么办?
回到问题本身。问题等价于下面的形式:求出一对
(i,j)
,使得
|sum[i]−sum[j]|
尽量接近
P
。注意到绝对值的形式,那么
不妨将前缀和从大到小排序。对于
i,j(j>i)
,从小到大枚举
j
,当
满足剩下的条件,注意细节即可。
#include<stdio.h>
#include<algorithm>
#include<deque>
#include<iostream>
#define MAXN 100005
using namespace std;
int N,T,R,L,Ans,P,Delta;
struct node{int id,v;}sum[MAXN];
bool operator<(node x,node y){if(x.v==y.v)return x.id<y.id;return x.v>y.v;}
void Solve()
{
Delta=L=R=1e9;
deque<int>Q;
int i,t,a,b,tmp;
for(i=0;i<=N;i++)
{
while(Q.size()&&sum[Q.front()].v-sum[i].v>=P)
{
t=Q.front();
tmp=sum[t].v-sum[i].v;
a=min(sum[t].id,sum[i].id);
b=max(sum[t].id,sum[i].id);
if(tmp-P<=Delta)
{
if(tmp-P==Delta)
{
if(L>=a)
{
if(L==a)R=min(R,b);
else L=a,R=b;
}
}
else
{
Ans=tmp;
L=a;R=b;
}
Delta=tmp-P;
}
Q.pop_front();
}
Q.push_back(i);
t=Q.front();
tmp=sum[t].v-sum[i].v;
a=min(sum[t].id,sum[i].id);
b=max(sum[t].id,sum[i].id);
if(P-tmp<=Delta&&Q.size()!=1)
{
if(P-tmp==Delta)
{
if(L>=a)
{
if(L==a)R=min(R,b);
else L=a,R=b;
}
}
else
{
Ans=tmp;
L=a;R=b;
}
Delta=P-tmp;
}
}
printf("%d %d %d\n",Ans,L+1,R);
}
int main()
{
int i,x;
scanf("%d%d",&N,&T);
for(i=1;i<=N;i++)
{
scanf("%d",&x);
sum[i].v=sum[i-1].v+x;
sum[i].id=i;
}
sort(sum,sum+N+1);
for(i=1;i<=T;i++)
{
scanf("%d",&P);
Solve();
}
}