在太阳西斜的这个世界里,置身天上之森。
等这场战争结束后,不归之人与望眼欲穿的人们,人人本着正义之名。
长存不灭的过去,逐渐消逝的未来。
我 回 来 了,
纵使日薄西山,即便看不到未来,
此时此刻的光辉,盼君勿忘。
——世上最幸福的女孩
题面说的很绕,第 2 2 2个操作,说了一堆期望什么的,最后发现要乘上 ( R − L + 1 ) (R-L+1) (R−L+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] ∀1≤i<k,∃hj∈[i×d−d+1,i×d]
因为每次只有插入一个数,所以我们发现 k d k_d kd一定会逐渐变大
考虑变化的量,应该是 ∑ i = 1 n ⌈ n i ⌉ \sum_{i=1}^n \lceil\frac{n}{i}\rceil ∑i=1n⌈in⌉左右,算出来大概是 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×t−d+1,d×t]里面的数的时候,才能够向后扩展,所以我们可以储存下来每一个 d d d现在的目标区间,就是 [ d × t − d + 1 , d × t ] [d\times t-d+1,d\times t] [d×t−d+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 l≤n,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;
}