NKOJ P3545 接近 (单调队列/二分查找)

时间限制 : 10000 MS   空间限制 : 165536 KB
问题描述

对于一个数字序列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 

6

样例输入3
7 2 
-2 -1 -2 4 1 -3 7 

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

 
 
二分

满足|sum[j]-sum[i-1]|>=p或者 |sum[j]-sum[i-1]|<=p
我想起老板说过可以把绝对值打开
打开后变一下形
可以得到 sum[j]+p<=sum[i-1];
           或者 sum[j]-p<=sum[i-1];
           这时候我们就可以用重新对sum数组复制成struct sum2 数组 按照
           val值进行排序 当然复制的id 也要保留 也就是我们要 对复制的struct 结构体数组排序
           然后在对此数组的va值复制成sum3数组
           然后就是用j枚举N
           然后二分对sum[j]-p和sum[j]+p在sum3数组里面二分查找>=的最小值
           id记在sum2 结构体里面  且sum3 和sum2 下标相同
           ans=min{abs(abs(sum3[二分的下标]-sum[j])(-或者+)p)}
      
           {
                r=j;
                l=sum2[枚举的下标].id+1;// if(ans>abs(abs(sum3[下标]-sum[j])(+/-)p))

          }
          if(ans==abs(abs(sum3[下标]-sum[j])-/+p))
{
if(l==sum2[下标].id+1&&r>j)
{
l=sum2[下标].id+1;
r=j;
}
if(l>sum2[下标].id+1)
{
l=sum2[下标].id+1;
r=j;
}
}
那么  cout<<abs(sum3[下标]-sum[j]);//符合条件的<<" "<<l<<" "<<r<<endl;
                                
//
#include<bits/stdc++.h>
using namespace std;
#define maxnn 102000
#define ll long long  
ll sum[maxnn];
ll sum3[maxnn];
ll n,t;
struct node
{
    ll id,va;
    
}sum1[maxnn];
bool cmp(node a,node b)
{
    if(a.va==b.va)
    return a.id<b.id;
    else
    return a.va<b.va;
    
}
int main()
{
    cin>>n>>t;
    ll x;
    ll uu;
    ll p;
    ll ans=1000000000000;
    sum1[0].id=0;
    sum1[0].va=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&x);
        sum[i]=sum[i-1]+x;
        sum1[i].va=sum[i];
        sum1[i].id=i;
    }
    sort(sum1,sum1+1+n,cmp);
    for(int i=0;i<=n;i++)
    {
        sum3[i]=sum1[i].va;
    }
    for(int i=1;i<=t;i++)
    {
        ans=100000000000;
        ll l=10000000000,r=10000000000;
        cin>>p;
        for(int j=1;j<=n;j++)
        {
            int xx=lower_bound(sum3,sum3+1+n,sum[j]+p)-sum3;//j
            int xxx=lower_bound(sum3,sum3+1+n,sum[j]-p)-sum3;
            if(ans>abs(abs(sum3[xx]-sum[j])-p))
            {
                uu=abs(sum3[xx]-sum[j]);
                ans=abs(abs(sum3[xx]-sum[j])-p);
                r=j;
                l=sum1[xx].id+1;
            }
            if(ans==abs(abs(sum3[xx]-sum[j])-p))
            {
                if(l==sum1[xx].id+1&&r>j)
                {
                    l=sum1[xx].id+1;
                    r=j;
                }
                if(l>sum1[xx].id+1)
                {
                    l=sum1[xx].id+1;
                    r=j;
                }
            }
            
        if(ans>abs(abs(sum3[xxx]-sum[j])-p))
            {
                uu=abs(sum3[xxx]-sum[j]);
                    ans=abs(abs(sum3[xxx]-sum[j])-p);
                    r=j;
                l=sum1[xxx].id+1;
            }
                if(ans==abs(abs(sum3[xxx]-sum[j])-p))
            {
                if(l==sum1[xxx].id+1&&r>j)
                {
                    l=sum1[xxx].id+1;
                    r=j;
                }
                if(l>sum1[xxx].id+1)
                {
                    l=sum1[xxx].id+1;
                    r=j;
                }
            }
            
        }
         cout<<uu<<" "<<l<<" "<<r<<endl;
         
    }
    
    
}

 单调队列

对每次询问都跑一遍。
显然第一种思路是行不通的,预处理出所有区间前缀和之差就是 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[队尾]
。这里显然满足单调队列模型。
满足剩下的条件,注意细节即可。
 
code:
#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();
    }
}

 

posted @ 2019-07-19 17:26  ALEZ  阅读(227)  评论(1编辑  收藏  举报