[Codeforces773E] [线段树] [找规律] Blog Post Rating

26 篇文章 0 订阅
23 篇文章 0 订阅

题意大概是
有一个博客,初始赞数为0,每个人会浏览这篇博客,如果这个人期望的赞数大于这边博客的赞数,他就会赞这篇博客,如果小于这篇博客,就踩一下(博客赞数-1),相同就不操作。询问对于每个i,1~i的人按照一个顺序浏览,求一种顺序使得博客最后的赞数最大,输出这个最大的赞数。

先瞎猜一下,这1~i个人肯定是按照期望的赞数升序访问最优。
然后写个暴力交一下,发现T了,说明这个结论是对的

那么只要维护一下这个序列就可以了。可以用权值线段树维护。
考虑怎么求这个按照这个升序顺序访问能得到的答案。

首先 ai ai 是当前排序后的数列中的第i个,不是原始数列的)是可能有负数的,那么肯定存在某个 x ,使ax<=x,那么在 x 的位置,博客的赞数为x,从 x+1 开始,博客的赞数就会单调不减。
f(i) 表示在 i 位置博客的赞数,那么f(i)=min(f(i1)+1,ai)
展开展开展开发现一个区间 [l,r] ,长度为 k
f(r)=min(f(l1)+k,al+k1,al+1+k2,......,ar1+1,ar)

那么每个区间维护一下 min(al+k1,al+1+k2,......,ar1+1,ar) 就好了

#include <cstdio>
#include <iostream>
#include <algorithm>
#define N 500010

using namespace std;

typedef pair<int,int> parii;

int n;
int a[N];
parii b[N];
struct func{
  int mn,tot;
  int val(int x){
    return min(x+tot,mn);
  }
  friend func operator *(func a,func b){
    a.tot+=b.tot;
    a.mn=min(b.mn,a.mn+b.tot);
    return a;
  }
};
struct seg{
  int l,r,mx;
  func f;
}T[N<<3];

void Build(int g,int l,int r){
  T[g].l=l; T[g].r=r; T[g].mx=-(1<<30);
  T[g].f.tot=0; T[g].f.mn=1<<30;
  if(l==r) return ;
  int mid=l+r>>1;
  Build(g<<1,l,mid); Build(g<<1|1,mid+1,r);
  T[g].f=T[g<<1].f*T[g<<1|1].f;
}

void Update(int g,int x,int y){
  T[g].mx=max(T[g].mx,y);
  if(T[g].l==T[g].r){
    T[g].f.tot++; T[g].f.mn=y;
    return ;
  }
  int mid=T[g].l+T[g].r>>1;
  if(x<=mid) Update(g<<1,x,y);
  else Update(g<<1|1,x,y);
  T[g].f=T[g<<1].f*T[g<<1|1].f;
}

int Calc(int g,int l,int r){
  if(T[g].l==l&&T[g].r==r) return T[g].f.tot;
  int mid=T[g].l+T[g].r>>1;
  if(r<=mid) return Calc(g<<1,l,r);
  if(l>mid) return Calc(g<<1|1,l,r);
  return Calc(g<<1,l,mid)+Calc(g<<1|1,mid+1,r);
}

int Find(int g,int l,int r){
  if(T[g].l==l&&T[g].r==r) return T[g].mx;
  int mid=T[g].l+T[g].r>>1;
  if(r<=mid) return Find(g<<1,l,r);
  if(l>mid) return Find(g<<1|1,l,r);
  return max(Find(g<<1,l,mid),Find(g<<1|1,mid+1,r));
}

func Query(int g,int l,int r){
  if(T[g].l==l&&T[g].r==r) return T[g].f;
  int mid=T[g].l+T[g].r>>1;
  if(r<=mid) return Query(g<<1,l,r);
  if(l>mid) return Query(g<<1|1,l,r);
  return Query(g<<1,l,mid)*Query(g<<1|1,mid+1,r);
}

inline bool check(int x){
  int num=Calc(1,1,x),last=Find(1,1,x);
  return last<=-num;
}

int main(){
  freopen("1.in","r",stdin);
  freopen("1.out","w",stdout);
  scanf("%d",&n);
  for(int i=1;i<=n;i++)
    scanf("%d",&a[i]),b[i]=parii(a[i],i);
  sort(b+1,b+1+n);
  for(int i=1;i<=n;i++) a[b[i].second]=i;
  Build(1,1,n);
  for(int i=1;i<=n;i++){
    Update(1,a[i],b[a[i]].first);
    int L=1,R=n,mid,cur=-1;
    while(L<=R) check(mid=L+R>>1)?L=(cur=mid)+1:R=mid-1;
    if(cur==-1) printf("%d\n",Query(1,1,n).val(0));
    else{
      int num=Calc(1,1,cur);
      if(cur==n){ printf("%d\n",-num); continue; }
      printf("%d\n",Query(1,cur+1,n).val(-num));
    }
  }
  return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值