YbtOJ#463-序列划分【二分答案,线段树,dp】

1|0正题

题目链接:https://www.ybtoj.com.cn/problem/463


1|1题目大意

给出长度为n的序列A,B。要求划分成若干段满足

  1. 对于任何i<j,若ij不是同一段的,要求满足Bi>Aj
  2. 每一段Ai的最大值的和不能超过m

要求最小化每一段Bi和的最大值。

n[1,105],Ai,Bi[1,109],m[1,1012]


1|2解题思路

最大值最小化很显然直接二分,然后变为求每一段Ai最大值的和的最小值。

第一个条件相当于限制了什么位置能够作为划分段的末尾,求一个前缀min{bi}和一个后缀max{ai}能够快速求出这些位置。

然后考虑dp,转移方程就是

fi=min{fj+max{ak}( k(j,i] )}

二分的条件限制了j的范围,加个指针就好了

这个东西好像很难搞,但是注意到vj=max{ak}这一部分是递减的,并且每次会让所有vi的一起和一个一起取max

因为是递减的,所以每次加入一个新的就相当于修改一段后缀的vi,然后求一个区间的最大fi+vi了。

可以线段树维护,每个节点维护该区间最大的fi+vi和最大的fi。区间推平vi的时候就可以拿最大的fi来更新fi+vi

时间复杂度O(nlognlogbi)


1|3code

#include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; const ll N=1e5+10,inf=1e9+7; ll n,m,a[N],b[N],pre[N],suf[N],last[N]; ll lg[N],st[N][17],v[N<<2],w[N<<2],lazy[N<<2]; void Downdata(ll x){ if(!lazy[x])return; lazy[x*2]=lazy[x*2+1]=lazy[x]; w[x*2]=v[x*2]+lazy[x]; w[x*2+1]=v[x*2+1]+lazy[x]; lazy[x]=0;return; } void Changew(ll x,ll L,ll R,ll l,ll r,ll val){ if(l>r)return; if(L==l&&R==r){w[x]=v[x]+val;lazy[x]=val;return;} ll mid=(L+R)>>1;Downdata(x); if(r<=mid)Changew(x*2,L,mid,l,r,val); else if(l>mid)Changew(x*2+1,mid+1,R,l,r,val); else Changew(x*2,L,mid,l,mid,val),Changew(x*2+1,mid+1,R,mid+1,r,val); w[x]=min(w[x*2],w[x*2+1]); } void Changev(ll x,ll l,ll r,ll pos,ll val){ if(l==r){v[x]=val;w[x]=v[x]+lazy[x];return;} ll mid=(l+r)>>1;Downdata(x); if(pos<=mid)Changev(x*2,l,mid,pos,val); else Changev(x*2+1,mid+1,r,pos,val); w[x]=min(w[x*2],w[x*2+1]); v[x]=min(v[x*2],v[x*2+1]); return; } ll Ask(ll x,ll L,ll R,ll l,ll r){ if(L==l&&R==r)return w[x]; ll mid=(L+R)>>1;Downdata(x); if(r<=mid)return Ask(x*2,L,mid,l,r); if(l>mid)return Ask(x*2+1,mid+1,R,l,r); return min(Ask(x*2,L,mid,l,mid),Ask(x*2+1,mid+1,R,mid+1,r)); } ll RMQ(ll l,ll r){ ll z=lg[r-l+1]; return max(st[l][z],st[r-(1<<z)+1][z]); } bool check(ll x){ memset(v,0x3f,sizeof(v)); memset(w,0x3f,sizeof(w)); memset(lazy,0,sizeof(lazy)); ll sum=0,l=0,tmp=v[0]; Changev(1,0,n,0,0); for(ll i=1;i<=n;i++){ sum+=b[i]; while(sum>x)l++,sum-=b[l]; Changew(1,0,n,last[i],i-1,a[i]); if(pre[i]<=suf[i+1])continue; tmp=Ask(1,0,n,l,i); Changev(1,0,n,i,tmp); } return (tmp<=m); } signed main() { freopen("sequence.in","r",stdin); freopen("sequence.out","w",stdout); scanf("%lld%lld",&n,&m); ll l=1,r=0;pre[0]=inf; for(ll i=1;i<=n;i++) scanf("%lld%lld",&a[i],&b[i]),r+=b[i],l=max(l,b[i]),st[i][0]=a[i]; for(ll i=2;i<=n;i++)lg[i]=lg[i>>1]+1; for(ll j=1;(1<<j)<=n;j++) for(ll i=1;i+(1<<j)-1<=n;i++) st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]); for(ll i=1;i<=n;i++){ ll l=1,r=i-1; while(l<=r){ ll mid=(l+r)>>1; if(RMQ(mid,i)>a[i])l=mid+1; else r=mid-1; } last[i]=r; } for(ll i=1;i<=n;i++) pre[i]=min(pre[i-1],b[i]); for(ll i=n;i>=1;i--) suf[i]=max(suf[i+1],a[i]); while(l<=r){ ll mid=(l+r)>>1; if(check(mid))r=mid-1; else l=mid+1; } check(l+1); printf("%lld\n",l); }

__EOF__

本文作者QuantAsk
本文链接https://www.cnblogs.com/QuantAsk/p/14407405.html
关于博主:退役OIer,GD划水选手
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   QuantAsk  阅读(43)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示