接近 单调队列

来源:互联网 发布:linux如何设置ss翻墙 编辑:程序博客网 时间:2024/06/08 13:17

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)可以是无序的。也就是说,i,j的大小关系在寻找答案没有影响,那么我们可以强行排序,就有单调性了。

不妨将前缀和从大到小排序。对于i,j(j>i),从小到大枚举j,当sum[i]sum[j]已经大于或等于P时,更大的j肯定不能得到更大的答案。当sum[]sum[]的值小于P时,在这个队列中满足差的绝对值最接近P的一对前缀和显然就是sum[]sum[]。这里显然满足单调队列模型。

满足剩下的条件,注意细节即可。


#include<stdio.h>#include<algorithm>#include<deque>#include<iostream>#define MAXN 100005using 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();    }}