题面
题解
神奇的分类讨论。
首先注意到每次所有跳蚤都只会往右跳,也就是说只要某一只跳蚤跳出了 max ( r i ) \max(r_i) max(ri) 它就不会再有贡献了。(和 火神的鱼 类似)
令 R = max ( r i ) R=\max(r_i) R=max(ri)。
考虑根号分治,将所有的跳蚤分成两类:将 t i > R t_i> \sqrt{R} ti>R 的分为第一类,将 t i ≤ R t_i\leq \sqrt{R} ti≤R 的分为第二类。
-
对于第一类,我们暴力维护这些鱼的位置,因为他们最多跳 R \sqrt{R} R 次就会跳出 R R R 了,也就是不会再有贡献了。
维护的过程可以用树状数组,时间复杂度 O ( q R log R ) O(q\sqrt R \log R) O(qRlogR)。
-
对于第二类,我们考虑对于每一种 t i t_i ti,维护一棵权值线段树,共 R \sqrt R R 棵,那么对于每一种 t i t_i ti,根据题目的询问 [ l , r ] [l,r] [l,r],我能还原这些跳蚤一开始应该在那个区间 [ l ′ , r ′ ] [l',r'] [l′,r′],然后在这颗线段树内查询这个区间。
共 R \sqrt R R 棵线段树,每棵查询 O ( log R ) O(\log R) O(logR),总时间 O ( q R log R ) O(q\sqrt R \log R) O(qRlogR)。
那么我们就能在 O ( q R log R ) O(q\sqrt R \log R) O(qRlogR) 的时间内维护所有操作了。
代码如下:
#include<bits/stdc++.h>
#define LN 17
#define SN 310
#define N 100010
#define lc(x) t[x].ch[0]
#define rc(x) t[x].ch[1]
#define lowbit(x) (x&-x)
using namespace std;
const int sn=300,n=100000;
struct data
{
int x,t;
data(){};
data(int a,int b){x=a,t=b;}
}p[N];
struct Tree
{
int ch[2],sum;
}t[N*LN];
int q,tot;
int node,root[SN];
int c[N];
void add(int x,int y)
{
for(;x<=n;x+=lowbit(x)) c[x]+=y;
}
int getsum(int x)
{
int ans=0;
for(;x;x-=lowbit(x)) ans+=c[x];
return ans;
}
void update(int &k,int l,int r,int x)
{
if(!k) k=++node;
t[k].sum++;
if(l==r) return;
int mid=(l+r)>>1;
if(x<=mid) update(lc(k),l,mid,x);
else update(rc(k),mid+1,r,x);
}
int query(int k,int l,int r,int ql,int qr)
{
if(!k) return 0;
if(ql<=l&&r<=qr) return t[k].sum;
int mid=(l+r)>>1,ans=0;
if(ql<=mid) ans+=query(lc(k),l,mid,ql,qr);
if(qr>mid) ans+=query(rc(k),mid+1,r,ql,qr);
return ans;
}
int main()
{
scanf("%d",&q);
int tim=0;
while(q--)
{
int opt;
scanf("%d",&opt);
if(opt==1)
{
int x,t;
scanf("%d%d",&x,&t);
if(t>sn)
{
p[++tot]=data(x,t);
add(x,1);
}
else update(root[t],-3e7,3e7,x-tim*t);//还原0时刻这只跳蚤应该在什么位置
}
if(opt==2)
{
tim++;
int tmp=0;
for(int i=1;i<=tot;i++)
{
add(p[i].x,-1);
data now=p[i];
now.x+=now.t;
if(now.x<=n)
{
p[++tmp]=now;
add(now.x,1);
}
}
tot=tmp;
}
if(opt==3)
{
int l,r;
scanf("%d%d",&l,&r);
int ans=getsum(r)-getsum(l-1);
for(int i=1;i<=sn;i++)
ans+=query(root[i],-3e7,3e7,l-i*tim,r-i*tim);//还原[l,r]区间在0时刻应该在哪个位置
printf("%d\n",ans);
}
}
return 0;
}