|
问题描述
对于一个数字序列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
二分
满足|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 ",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(); } }