[Ynoi2015]我回来了

10 篇文章 0 订阅
4 篇文章 0 订阅

在太阳西斜的这个世界里,置身天上之森。
等这场战争结束后,不归之人与望眼欲穿的人们,人人本着正义之名。
长存不灭的过去,逐渐消逝的未来。
我 回 来 了
纵使日薄西山,即便看不到未来,
此时此刻的光辉,盼君勿忘。
——世上最幸福的女孩

题面说的很绕,第 2 2 2个操作,说了一堆期望什么的,最后发现要乘上 ( R − L + 1 ) (R-L+1) (RL+1),所以就是求情况之和嘛

我们考虑亵渎什么时候可以被触发

首先他一定会触发一次,那么这个时候需要让场上所有人的血量 − d -d d,想要触发下一次,一定需要有一个人的血量在 [ 1 , d ] [1,d] [1,d]的范围内,如果触发第二次呢?不难发现就是需要有一个人的血量在 [ d + 1 , 2 d ] [d+1,2d] [d+1,2d]的范围内,以此类推

所以对于伤害值为 d d d的情况,亵渎能够被触发的次数就是最大的 k k k,使得 ∀ 1 ≤ i < k , ∃ h j ∈ [ i × d − d + 1 , i × d ] \forall 1\leq i< k,\exists h_j\in[i\times d-d+1,i\times d] 1i<k,hj[i×dd+1,i×d]

因为每次只有插入一个数,所以我们发现 k d k_d kd一定会逐渐变大

考虑变化的量,应该是 ∑ i = 1 n ⌈ n i ⌉ \sum_{i=1}^n \lceil\frac{n}{i}\rceil i=1nin左右,算出来大概是 1.2 × 1 0 6 1.2\times 10^6 1.2×106,可以证明这个东西 < n log ⁡ n <n\log n <nlogn,也就是说最多只会变化 n log ⁡ n n\log n nlogn

那么我们可以形成一个基本的思路就是每次插入一个数的时候,快速的找到需要更改的位置,然后暴力更改,后面那个东西可以利用一个树状数组进行维护

考虑快速插入的时候怎么找到需要更改的位置

我们发现,当伤害值为 d d d,当前次数为 t t t的时候,我们只有插入的一个在 [ d × t − d + 1 , d × t ] [d\times t-d+1,d\times t] [d×td+1,d×t]里面的数的时候,才能够向后扩展,所以我们可以储存下来每一个 d d d现在的目标区间,就是 [ d × t − d + 1 , d × t ] [d\times t-d+1,d\times t] [d×td+1,d×t],然后插入 x x x的时候找到所有包含 x x x的区间暴力更新,更新之后插入新的目标区间


因为最多只会被查找 n log ⁡ n n\log n nlogn次,我们尝试每次用 log ⁡ n \log n logn的时间查找一个满足条件的区间,那么这个时候复杂度是 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n),是基本可以接受的

那么问题就转化成了每次求包含一个点 x x x的每一个满足条件的区间

我们可以使用线段树+set的方法来解决

我们建立 n n n个set, s e t [ i ] set[i] set[i]储存的是所有左端点为 i i i的区间的右端点的值

再建立一棵线段树,每个节点储存左端点位于子区间中的区间的右端点的最大值,线段树的每个叶子节点就相应的对应每一个set

对于一个 x x x,每次我们不停地查找线段树上的 [ 1 , x ] [1,x] [1,x]这一段区间,每次查询出来的就是一个满足条件的,直到 [ 1 , x ] [1,x] [1,x]这一段区间的最大右端点 < x <x <x,就一定没有满足条件的区间了,停止查询

怕被卡常,所以这里我选择了手写平衡树


但是这样的写法还有一些细节

比如说我们每次真的只会往后扩展一次吗?

比如说先插入了一个 6 6 6,对于 d = 3 d=3 d=3来说,不能更新

接下来插入一个 2 2 2,这个时候 d = 3 d=3 d=3不是只更新一次,因为杀掉 2 2 2之后还可以杀掉 6 6 6,这个时候会更新两次

因为我们只储存了每个 d d d当前的目标区间,还可能后面有但是当时不能更新的,所以我们需要再开一个树状数组来储存小兵的情况,每次拓展多次

这一段的代码:

        if(opt==1){
            bit2.add(x,1);//x位置多了一个人
            while(1){
                node now=seg.query(1,1,n,1,x);//查找当前l在[1,x]中r最大的区间
                if(now.max<x)break;//如果比x小,就没有了,结束循环
                treap.del(now.maxid,now.max),seg.update(1,1,n,now.maxid);//在平衡树中删去这一条线段,同时线段树的信息需要更新
                int bel=now.max-now.maxid+1,l=now.maxid,r=now.max;//r-l+1就是这个点的d,尝试向右拓展
                while(bit2.ask(r)-bit2.ask(l-1)>0&&l<=n)//如果这个区间有小兵
                    l+=bel,r+=bel,bit1.add(bel,1);//更新答案
                if(l<=n)treap.ins(l,r),seg.update(1,1,n,l);//如果还在[1,n]里面就插入新的目标线段
            }
        }

另外树状数组的代码也需要相应修改,因为我们可能查找到一段的 l ≤ n , r > n l\leq n,r>n ln,r>n,这个同样是合法的,但是查询的时候可能会出错,因为 b i t [ r ] = 0 bit[r]=0 bit[r]=0,所以树状数组查询时,如果 o > n o>n o>n o = n o=n o=n即可

空间复杂度写回收的话 O ( n ) O(n) O(n),时间复杂度大约 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)

要写两棵树状数组+一棵线段树+一棵平衡树,我选择了结构体封装的版本,同时表达信仰啊qwq

chtholly->维护答案的树状数组
almeria->维护小兵的树状数组
ithea->维护区间的平衡树
nephren->线段树

 #include <bits/stdc++.h>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)

typedef long long ll;

const int N=1e5+5;

template<typename T> void read(T &x){
   x=0;int f=1;
   char c=getchar();
   for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
   for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
    x*=f;
}

int n,m;
int son[N*10][2],siz[N*10],val[N*10],treap[N*10];
int bin[N*10],binsiz;
int root[N],tot;

struct node{
    int maxid,max;
}seg[N<<2];

struct chtholly_almeria{
    int bit[N];
    int lowbit(int o){
        return o&-o;
    }
    void add(int o,int x){
        for(;o<=n;o+=lowbit(o))bit[o]+=x;
    }
    int ask(int o){
    	if(o>n)o=n;
        int res=0;
        for(;o;o-=lowbit(o))res+=bit[o];
        return res;
    }
}Chtholly,Almeria;

struct ithea{
    int newnode(){
        if(!binsiz)return ++tot;
        else return bin[binsiz--];
    }
    void delnode(int x){
        bin[++binsiz]=x;
        siz[x]=val[x]=treap[x]=son[x][0]=son[x][1]=0;
    }
    void update(int x){
        siz[x]=siz[son[x][0]]+siz[son[x][1]]+1;
    }
    int merge(int u,int v){
        if(!u||!v)return u|v;
        int rt;
        if(treap[u]<treap[v])son[rt=u][1]=merge(son[u][1],v);
        else son[rt=v][0]=merge(u,son[v][0]);
        return update(rt),rt;
    }
    void split(int o,int &u,int &v,int k){
        if(!o){u=v=0;return;}
        if(val[o]<=k)split(son[u=o][1],son[o][1],v,k);
        else split(son[v=o][0],u,son[o][0],k);
        update(o);
    }
    void ins(int l,int r){
        int lft,rht;
        split(root[l],lft,rht,r);
        int u=newnode();
        treap[u]=rand(),siz[u]=1,val[u]=r;
        root[l]=merge(merge(lft,u),rht);
    }
    void del(int l,int r){
        int lft,mid,rht;
        split(root[l],lft,rht,r);
        split(lft,lft,mid,r-1);
        root[l]=merge(lft,rht);
        delnode(mid);
    }
    int max(int l){
        int u=root[l];
        while(son[u][1])u=son[u][1];
        return val[u];
    }
}Ithea;

struct nephren{
    # define lc (u<<1)
    # define rc (u<<1|1)
    node merge(node l,node r){
        if(l.max>r.max)return l;
        else return r;
    }
    void update(int u,int l,int r,int x){
        if(l==r){
            if(root[l])seg[u].max=Ithea.max(l),seg[u].maxid=l;
            else seg[u].max=seg[u].maxid=0;
            return;
        }   
        int mid=l+r>>1;
        if(x<=mid)update(lc,l,mid,x);
        else update(rc,mid+1,r,x);
        seg[u]=merge(seg[lc],seg[rc]);
    }
    node query(int u,int l,int r,int ql,int qr){
        if(l>=ql&&r<=qr)return seg[u];
        int mid=l+r>>1;
        if(qr<=mid)return query(lc,l,mid,ql,qr);
        if(ql>mid)return query(rc,mid+1,r,ql,qr);
        return merge(query(lc,l,mid,ql,qr),query(rc,mid+1,r,ql,qr));
    }
}Nephren;

int main()
{
    // freopen("testdata.in","r",stdin);
    srand(19260817);
    read(n),read(m);
    Rep(i,1,n){
        Chtholly.add(i,1);
        Ithea.ins(1,i),Nephren.update(1,1,n,1);
    }
    Rep(i,1,m){
        int opt,x,y;
        read(opt),read(x);
        if(opt==1){
            Almeria.add(x,1);
            while(1){
                node now=Nephren.query(1,1,n,1,x);
                if(now.max<x)break;
                Ithea.del(now.maxid,now.max),Nephren.update(1,1,n,now.maxid);
                int bel=now.max-now.maxid+1,l=now.maxid,r=now.max;
                while(Almeria.ask(r)-Almeria.ask(l-1)>0&&l<=n)
                    l+=bel,r+=bel,Chtholly.add(bel,1);
                if(l<=n)Ithea.ins(l,r),Nephren.update(1,1,n,l);
            }
        }
        else read(y),printf("%d\n",Chtholly.ask(y)-Chtholly.ask(x-1));
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值