【线段树】【积累】牛客练习赛70 F曲调
【线段树】【积累】牛客练习赛70 F曲调
题意:
给定一个长度为 \(n\) 的序列 \(a\), \(n\leq 100000,\,|a_i|\leq10^9\) ,有 \(m\) 个询问 。 每个询问给出 \(l,r\) ,要求区间 \([l,r]\) 绝对值最小的子段和(子段不为空)。
叨叨:
打练习赛的时候考虑了用莫队离散,然后用线段树+set查询和修改记录,但是莫队需要维护 \(l\) 和 \(r\) ,但是修改会相互影响之前的值,太难实现了。
完全没想到固定一个端点的做法qaq。想到了也想不出题解这种复杂度的做法。
到底还是因为没习惯这种套路。
题解:
对询问以 \(r\) 递增排序,然后枚举 \(r\) 来更新以 \(r\) 为右端点的所有区间的答案。
预先处理数组的前缀和,记为 \(sum\) 。
对于当前 \(x=r\) ,求 最大的 \(x_1\) 满足 \(x_1<x\) 使得 \(sum_{x{1}}\leq sum_x\) ,然后对满足 \(l\leq x_1+1\) 的 \(l\) 的更新答案 \(min(ans_l,sum_x-sum_{x_1})\) ,再求最大的 \(x_2\) 满足 \(x_2<x_1\) 使得 \(sum_{x_1}\leq sum_{x_2}\leq sum_x\) ,然后对满足 \(l\leq{x_2+1}\) 的 \(l\) 更新答案 \(min(ans_l,sum_x-sum_{x_2})\)。按照上面步骤执行下去,直到 \(sum_x=sum_{x_i}\) 或 \(x_i=1\) 为止。
此时每个点寻找 \(x\) 的次数最糟会达 \(\mathcal{O(n)}\) ,需要优化。
可以发现,每当寻找点 \(x_i\) ,若 \(sum_{x_i}-sum_{x_{i-1}}\leq sum_x-sum_{xi}\) ,则该 \(x_i\) 对答案无贡献,所以每次可以只寻找满足 \(\lceil\frac{sum_x+sum_{x_{i-1}}}2\rceil\leq sum_{x_i}\leq sum_x\) 的最大 \(x_i\) 即可,每次寻找不会超过上一次的一般,总共需要寻找 \(\mathcal{O}(W)\) , \(W\) 为 \(sum\) 的值域。
对于 \(sum_{x_1}\geq sum_x\) 用相同方法再处理一遍。
用主席树寻找 \(x_i\) ,用线段树维护 \(min\) ,时间复杂度 \(\mathcal{O}(n\,logn\,logW)\) 。
代码:
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define mkp(a,b) make_pair(a,b)
using namespace std;
typedef long long ll;
const int maxn=5e5+5;
const int inf=0x3f3f3f3f;
const int mod=998244353;
int n,m;
ll h[maxn];int cnt;
inline int id(ll x){return lower_bound(h+1,h+1+cnt,x)-h;}
int a[maxn];ll sum[maxn];
struct Q{
int l,r,id;
bool operator<(const Q&p)const{return r<p.r;}
}q[maxn];
int T[maxn],L[maxn<<2],R[maxn<<2],num[maxn<<2],tnt;
void update(int &rt,int pre,int l,int r,int x)
{
if(!rt||rt==pre){
rt=++tnt;L[rt]=L[pre];R[rt]=R[pre];num[rt]=num[pre];
}
num[rt]++;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)update(L[rt],L[pre],l,mid,x);
else update(R[rt],R[pre],mid+1,r,x);
}
int qma(int rt,int pre,int l,int r,int ql,int qr)
{
if(!(num[rt]-num[pre]))return 0;
if(l==r)return l;
int mid=(l+r)>>1,tres=0;
if(qr>mid&&(num[R[rt]]-num[R[pre]]))tres=qma(R[rt],R[pre],mid+1,r,ql,qr);
if(tres)return tres;
return qma(L[rt],L[pre],l,mid,ql,qr);
}
const ll UP=1e16;
ll mi[maxn<<2],ly[maxn<<2];
void build(int k,int l,int r)
{
ly[k]=UP;
if(l==r){
mi[k]=abs(a[l]);return;
}
int mid=(l+r)>>1;
build(k<<1,l,mid);build(k<<1|1,mid+1,r);
mi[k]=min(mi[k<<1],mi[k<<1|1]);
}
void down(int k)
{
ly[k<<1]=min(ly[k<<1],ly[k]);ly[k<<1|1]=min(ly[k<<1|1],ly[k]);ly[k]=UP;
mi[k<<1]=min(mi[k<<1],ly[k<<1]);mi[k<<1|1]=min(mi[k<<1|1],ly[k<<1|1]);
}
void update(int k,int l,int r,int ql,int qr,ll v)
{
if(ql>r||qr<l)return;
if(ql<=l&&r<=qr){
mi[k]=min(mi[k],v);ly[k]=min(ly[k],v);
return;
}
if(ly[k]!=UP)down(k);
int mid=(l+r)>>1;
update(k<<1,l,mid,ql,qr,v);update(k<<1|1,mid+1,r,ql,qr,v);
mi[k]=min(mi[k<<1],mi[k<<1|1]);
}
ll query(int k,int l,int r,int ql,int qr)
{
if(ql>r||qr<l)return UP;
if(ql<=l&&r<=qr)return mi[k];
if(ly[k]!=UP)down(k);
int mid=(l+r)>>1;
return min(query(k<<1,l,mid,ql,qr),query(k<<1|1,mid+1,r,ql,qr));
}
void solve(int r)
{
ll sr=sum[r],sl=h[1];int t,rr=r;
update(1,1,n,1,1,abs(sr));
while(sr>sl&&rr>1)
{
t=qma(T[id(sr)],T[id(sl)-1],1,n,1,rr-1);
// printf("(%lld %lld %d %d %lld) ",sl,sr,rr,t,sum[t]);
if(!t)break;
sl=sum[t];rr=t;
update(1,1,n,1,t+1,sr-sl);
sl=(sl+sr+1)>>1;
}
sr=sum[r];sl=h[cnt];rr=r;
while(sr<sl&&rr>1)
{
t=qma(T[id(sl)],T[id(sr)-1],1,n,1,rr-1);
// printf("(%lld %lld %d %d %lld) ",sl,sr,rr,t,sum[t]);
if(!t)break;
sl=sum[t];rr=t;
update(1,1,n,1,t+1,sl-sr);
sl=(sl+sr+1)>>1;
}
}
struct TT{
ll v;int id;
bool operator<(const TT&p)const{return v<p.v;}
}pt[maxn];
ll res[maxn];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
h[++cnt]=sum[i];
pt[i]={sum[i],i};
}
build(1,1,n);
sort(h+1,h+1+cnt);
cnt=unique(h+1,h+1+cnt)-h-1;
sort(pt+1,pt+1+n);
for(int i=1,idd;i<=n;i++){
idd=id(pt[i].v);
update(T[idd],T[idd-1],1,n,pt[i].id);
}
for(int i=1,l,r;i<=m;i++)
{
scanf("%d%d",&l,&r);
q[i]={l,r,i};
}
sort(q+1,q+1+m);
int st=0;
for(int i=1;i<=m;i++)
{
while(st<q[i].r)solve(++st);
res[q[i].id]=query(1,1,n,q[i].l,q[i].r);
}
for(int i=1;i<=m;i++)printf("%lld\n",res[i]);
}