IOI2005/Mountains/洛谷P5848 题解

这道题要我们维护区间加,求大于一个数的最短前缀长度。

我们可以用线段树做到这一点,每个节点储存 区间和 以及 区间最大前缀 两个值。维护它十分容易,因为区间最大前缀必定是左子区间的最大前缀或者左子区间的和和右子区间的最大前缀的和。查询时,我们从根节点往下走,如果当前数不小于左子区间的最大前缀,则说明答案在右子区间,反之在左子区间。这样,我们就可以在 $O(\rm log n)$ 的时间里完成一次询问。

但是,此题由于值域巨大,必须动态开点或者离散化,前者好写点但容易炸空间,这里选择用离散化:
我们使树上每个节点保存左开右闭(便于修改),把每次修改的两端以及它们减一的点进行离散化,然后再用一个数组储存原值,这样可以方便地在树上带入原值算。这里每个点减一的点也要离散化的原因是,在线段树维护序列的过程中,我们需要知道每一段值相同的点的两端才能准确地更新及计算。

举个例子:
 

I 1 8 2
I 2 4 1
I 8 9 3

这两个操作后,实际序列上下标对应的值应该是:

1-1 2
2-4 1
5-7 2(节点左开右闭,5不用离散)
8-9 3

$7$号点也要离散化,放到树里面去了。(当然,开闭方向并不唯一)

剩下的就是细节了,见代码和注释:

#include<bits/stdc++.h>
#define N 400009
#define INF 0x3f3f3f3f
#define mod 1000000007
#define int long long
using namespace std;
typedef long double ldb;
typedef pair<int,int> pii;
int n,m,cnt,ct2;
int fg[N],tmp[N];
unordered_map<int,int> mp;
struct node{
  int l,r,sum,lsum,fl;//fl表示覆盖标记,覆盖成多少
  bool bk;//bk表示有没有覆盖标记
}t[N*4];
struct nd{
  int l,r,k;
}q[N];
void pushup(int x){
  t[x].sum=(t[x*2].sum+t[x*2+1].sum);
  t[x].lsum=max(t[x*2].lsum,t[x*2].sum+t[x*2+1].lsum);
}
void pushdown(int x){
  if(!t[x].bk)return;
  t[x].bk=0;
  int k=t[x].fl;
  t[x*2].sum=(fg[t[x*2].r]-fg[t[x*2].l-1])*k,t[x*2].lsum=max((int)0,t[x*2].sum);
  t[x*2].fl=t[x].fl,t[x*2].bk=1;
  t[x*2+1].sum=(fg[t[x*2+1].r]-fg[t[x*2+1].l-1])*k,t[x*2+1].lsum=max((int)0,t[x*2+1].sum);
  t[x*2+1].fl=t[x].fl,t[x*2+1].bk=1;
}//区间长度r-l而非r-l+1,表示左开右闭
void build(int x,int l,int r){
  t[x].l=l,t[x].r=r;
  if(l==r){return;}
  int mid=(l+r)/2;
  build(x*2,l,mid);build(x*2+1,mid+1,r);
  pushup(x);
}
void change(int x,int l,int r,int k){
  if(l<=t[x].l&&r>=t[x].r){
    t[x].sum=(fg[t[x].r]-fg[t[x].l-1])*k,t[x].lsum=max((int)0,t[x].sum),t[x].fl=k,t[x].bk=1;
    return;
  }
  pushdown(x);
  int mid=(t[x].l+t[x].r)/2;
  if(l<=mid)change(x*2,l,r,k);
  if(r>mid)change(x*2+1,l,r,k);
  pushup(x);
}
int ask(int x,int h){
  if(x==1&&h>=t[x].lsum)return n;
  if(t[x].l==t[x].r)return t[x].sum?fg[t[x].l-1]+h/((t[x].sum)/(fg[t[x].l]-fg[t[x].l-1])):fg[t[x].l];
//走到这里到底了,判断是能走到这个节点还是被卡在中间
  pushdown(x);
  if(h>=t[x*2].lsum){return ask(x*2+1,h-t[x*2].sum);}//往右走
  else return ask(x*2,h);//往左走
}
signed main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  cin>>n;
  while(1){
    char op;int l,r,k;
    cin>>op;
    if(op=='E')break;
    if(op=='I'){cin>>l>>r>>k;q[++cnt]={l,r,k};}
    if(op=='Q'){cin>>k;q[++cnt]={0,0,k};}
  }
  for(int i=1;i<=cnt;i++){
    if(q[i].l&&q[i].r)tmp[++ct2]=q[i].l,tmp[++ct2]=q[i].r,tmp[++ct2]=q[i].l-1,tmp[++ct2]=q[i].r-1;
  }
  sort(tmp+1,tmp+ct2+1);ct2=unique(tmp+1,tmp+ct2+1)-tmp-1;
  for(int i=1;i<=ct2;i++)mp[tmp[i]]=i,fg[i]=tmp[i];
  build(1,1,ct2);
  for(int i=1;i<=cnt;i++){
    if(q[i].l&&q[i].r)change(1,mp[q[i].l],mp[q[i].r],q[i].k);
    else{
      cout<<ask(1,q[i].k)<<"\n";
    }
  }
  return 0;
}

  • 15
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值