金题大战Vol.0 B、序列
题目描述
给定两个长度为 (n) 的序列(a), (b)。
你需要选择一个区间([l,r]),使得(a_l+…+a_r>=0)且(b_l+…+b_r>=0)。最大化你选择的区间长度。
输入格式
第一行一个整数(n),第二行(n)个整数(a_1-a_n),第三行n个整数(b_1-b_n)。
输出格式
一行一个整数表示(max(r-l+1))。保证至少有一个区间满足条件。
样例
样例输入
5
2 -4 1 2 -2
-2 3 1 -3 1
样例输出
1
数据范围与提示
对于(20\%) 的数据,(n<=5000)。
对于(60\%) 的数据,(n<=10^5)。
对于(100\%) 的数据,(1<=n<=10^6,|ai|, |bi|<=10^9)。 数据有一定梯度。
分析
乍看上去这一道题似乎不太好处理,要同时满足下标、(a)、(b)三个条件
突破点就在于怎么把限制条件一维一维地删去
首先我们把题目中给出的数组转化成前缀和的形式
即 (suma[r]>=suma[l],sumb[r]>=sumb[l])
我们将 (suma) 从小到大排一下序
这样我们每一次从左到右遍历,就相当于消去了一维
我们只考虑 (sumb) 和坐标的关系即可
这种关系我们可以用树状数组去维护,即把 (sumb) 的值作为树状数组的下标,把原先的编号作为树状数组的权值
这样在每次遇到一个点时,我们在树状数组中查询 (sumb) 比它小的最小的下标
同时更新下标为 (sumb) 的节点的值为当前点的编号
(sumb) 比较大,并且有负数,因此考虑离散化
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
typedef long long ll;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
int a[maxn],b[maxn],n,cnt,tr[maxn];
ll suma[maxn],sumb[maxn];
struct asd{
int wz;
ll jla,jlb;
}jl[maxn];
bool cmp(asd aa,asd bb){
return aa.jla<bb.jla;
}
int lb(int xx){
return xx&-xx;
}
void ad(int wz,int val){
for(int i=wz;i<=n;i+=lb(i)){
tr[i]=min(tr[i],val);
}
}
int cx(int wz){
int ans=0x3f3f3f3f;
for(int i=wz;i>0;i-=lb(i)){
ans=min(ans,tr[i]);
}
return ans;
}
int main(){
freopen("B.in","r",stdin);
freopen("B.out","w",stdout);
int ans=1,bef,wz;
memset(tr,0x3f,sizeof(tr));
n=read();
for(register int i=1;i<=n;i++){
a[i]=read();
suma[i]=suma[i-1]+(ll)a[i];
}
for(register int i=1;i<=n;i++){
b[i]=read();
sumb[i]=sumb[i-1]+(ll)b[i];
}
for(register int i=1;i<=n;i++){
jl[i].jla=suma[i];
jl[i].jlb=sumb[i];
if(jl[i].jla>=0 && jl[i].jlb>=0){
ans=max(ans,i);
}
jl[i].wz=i;
}
sort(jl+1,jl+1+n,cmp);
sort(sumb+1,sumb+1+n);
cnt=unique(sumb+1,sumb+1+n)-sumb-1;
for(int i=1;i<=n;i++){
wz=lower_bound(sumb+1,sumb+cnt+1,jl[i].jlb)-sumb;
bef=cx(wz);
if(bef>=jl[i].wz){
ad(wz,jl[i].wz);
continue;
}
ans=max(ans,jl[i].wz-bef);
ad(wz,jl[i].wz);
}
printf("%d
",ans);
return 0;
}