接近 单调队列

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 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();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值