1 条题解

  • 0
    @ 2026-5-5 23:58:04

    线段树具有很优秀的性质并且易于扩展。例如,对于懒标记的下传操作,我们可以使用标记永久化取消。而这道题则对上传操作进行了扩展,利用了线段树树高只有 log\log 层的性质,对区间贡献的情况进行了分类讨论(实质为在线段树这一结构上二分)来统计贡献。

    天数区间很大,于是我们考虑维护下标区间 [1,1014][1,10^{14}] 的动态开点线段树。思考我们要维护哪些信息。

    1. 显然,我们要维护答案区间 ansans
    2. 对于一个区间,我们需要知道其对之后区间的干草捆数贡献 remrem
    3. 为了在合并时知道贡献,我们需要维护当前区间有多少天还未有干草。由于区间范围过大,我们转而维护当前区间有干草吃的天数 numnum
    4. 知道了左区间对之后区间干草捆数贡献,如何查找左区间当前左区间对右区间的贡献 nopnop?我们另开一个函数求解此信息,函数作用为:求解区间 [l,r][l,r] 在左边有 xx 捆干草送入(且自身已有一定的贡献)的情况下会对答案数组有多少贡献。

    考虑实现该函数,我们利用 numnum 数组进行判断:

    1. 如果 xx 会在 [l,mid][l,mid] 内消耗完,那么就对 [mid+1,r][mid+1,r] 没有影响,求解 xx[l,mid][l,mid] 之内的贡献,再加上右区间内部的贡献。发现右区间内部贡献就是右儿子的 nopnop,因此右区间的查询是 O(1)O(1) 的。
    2. 否则说明 [l,mid][l,mid] 中每一天都有干草吃,对其 O(1)O(1) 计算,将 xx 减去左区间没有干草吃的天数,对右区间进行求解。

    上面两种情况都只会对一个儿子向下求解,因此其复杂度为 logn\log n。所以总复杂度为 nlog2nn\log^2 n,可以通过此题。

    #include<bits/stdc++.h>
    #define endl '\n'
    #define ll long long
    #define pre(i,a,b) for(int i=a;i<=b;++i)
    using namespace std;
    ll read(ll t = 0, bool f = 1, char c = 0) {
    	while(!isdigit(c = getchar())) f = c^45;
    	while(isdigit(c)) t = (t << 1) + (t << 3) + (c ^ 48), c = getchar();
    	return f ? t: -t;
    }
    const int N=1e7+5,p=1e9+7,i2=500000004;
    int tot, rt=1;
    #define mid ((l+r)>>1)
    array<int,N> ls,rs;
    array<ll,N> ans,num,rem,nop;
    int plu(int u,int v) { return u+v>=p?u+v-p:u+v; }
    int mul(int u,int v) { return (1ll*u*v)%p; }
    int sum(ll n) { return mul(mul(n%p,(n+1)%p),i2); }
    int range(ll l,ll r) { assert(l<=r);return plu(sum(r),p-sum(l-1)); }
    int query(int u,ll l,ll r,ll x) {
      if(l==r) return x?l%p:ans[u];
      if(mid-l+1-num[ls[u]]>=x) return plu(query(ls[u],l,mid,x),nop[u]);
      else return plu(range(l,mid),query(rs[u],mid+1,r,x-(mid-l+1-num[ls[u]])+rem[ls[u]])); 
    }
    void pu(int u,ll l,ll r) {
      num[u]=num[ls[u]]+min(num[rs[u]]+rem[ls[u]],r-mid);
      rem[u]=rem[rs[u]]+max(num[rs[u]]+rem[ls[u]]-(r-mid),0ll);
      ans[u]=plu(ans[ls[u]],(nop[u]=query(rs[u],mid+1,r,rem[ls[u]])));
    }
    void add(ll f,ll val,int &u=rt,ll l=1,ll r=2e14) {
      if(u==0) u=++tot;
      if(l==r) {
        if(val==0) ans[u]=rem[u]=num[u]=0;
        else ans[u]=l%p,num[u]=1,rem[u]=val-1;
        return;
      }
      if(f<=mid) add(f,val,ls[u],l,mid);
      else add(f,val,rs[u],mid+1,r);
      pu(u,l,r);
    }
    void B() {
      ll q=read(),d,b;;
      tot=1;
      while(q--) {
        d=read(),b=read();
        add(d,b);
        printf("%lld\n",ans[rt]);
      }
    }
    signed main() {
      int T;T=1;B();
      return 0;
    }
    
    • 1

    信息

    ID
    7677
    时间
    6000ms
    内存
    512MiB
    难度
    8
    标签
    递交数
    21
    已通过
    6
    上传者