【题目背景】
小卡正在新家的客厅中看电视。电视里正在播放放了千八百次依旧重播的《亮剑》,剧中李云龙带领的独立团在一个县城遇到了一个鬼子小队,于是独立团与鬼子展开游击战。
【题目描述】
县城里有 n n n个用地道相连的房子,第 i i i个只与第 i − 1 i-1 i−1和第 i + 1 i+1 i+1个相连。这时有 m m m个消息依次传来
消息为 D    x D\;x Dx:鬼子将 x x x号房子摧毁了,地道被堵上。
消息为 R R R :村民们将鬼子上一个摧毁的房子修复了。
消息为 Q    x Q\;x Qx:有一名士兵被围堵在 x x x号房子中。
李云龙收到信息很紧张,他想知道每一个被围堵的士兵能够到达的房子有几个。
【输入格式】
第一行2个整数 n , m n,m n,m(n,m<=50000)。
接下来 m m m行,有如题目所说的三种信息共 m m m条。
【输出格式】
对于每一个被围堵的士兵,输出该士兵能够到达的房子数。
S a m p l e    I n p u t Sample\;Input SampleInput
7 9
D 3
D 6
D 5
Q 4
Q 5
R
Q 4
R
Q 4
S a m p l e    O u t p u t Sample\;Output SampleOutput
1
0
2
4
【题意分析】
首先,读完题目,我笑了半天。
//tm《亮剑》都能编题目
这道题目很明显是平衡树,但是我们可以用一种特殊的平衡树:
# f h q    T r e a p fhq\;Treap fhqTreap 无旋Treap
//fhq大佬orz
如果你想学 S p l a y Splay Splay然后被 r o t a t e ( ) rotate() rotate()转晕了,那么可以兼容Splay所有功能的fhq Treap绝对是不二之选:
(https://www.luogu.org/blog/2662945155-minions/fhq-treap-xue-xi-bi-ji)
(https://blog.csdn.net/CABI_ZGX/article/details/79963427)
再回到本题,直接在Treap上删点插点比较麻烦,那么我们就反向思考一下:每次轰炸的时候,将被轰炸的节点放进Treap,修复的时候反而拿出来,至于询问则非常简单:找到这个点的前驱,后继,相减就是能到达的房子总数。
注意到修复操作有一个性质:将上一个被轰炸的房子修复,那么简单地维护一个栈,轰炸了就 S . p u s h ( ) S.push() S.push(),修复的时候取栈顶 S . t o p ( ) S.top() S.top(),然后 S . p o p ( ) S.pop() S.pop()弹出
如果被询问的点被轰炸了且没有修复,那么直接输出0。
//蒟蒻用stl自带的栈奇怪地RE,然后改了手写栈就对了。其实并没有什么区别
//为了维护Treap,要跟Splay一样插♂哨兵,但是插♂-INF和INF会错,后来看了下题解其实应该插♂节点0和节点n+1,不然你访问的东西会很奇怪(用了传参)
Code:
#include
#include
#include
#include
#include
#include
#define MAX 200000
#define INF 1 << 29
using namespace std;
struct fhq_Treap{
int son[5];
int size,v,rand;
}tree[MAX]; //Treap结构体
int root,n,q,cnt;
bool Destroyed[MAX]; //是否被轰炸
int stack[MAX],top; //zz手写栈
inline int read (){
int s = 0,w = 1;
char ch = getchar ();
while (!isdigit (ch)){if (ch == '-')w = -1;ch = getchar ();}
while (isdigit (ch)){s = (s << 3)+(s << 1)+ch-'0';ch = getchar ();}
return s*w;
}
inline void push_up (int x){
if (!x)return;
tree[x].size = tree[tree[x].son[0]].size+tree[tree[x].son[1]].size+1;
} //跟Splay一样,向上更新子树大小
inline int rank(int x,int k){
if (!x)return 0; //如果没有了就退出
if (k<=tree[x].v)return rank(tree[x].son[0],k);
else return rank(tree[x].son[1],k)+tree[tree[x].son[0]].size+1;
} //拿到Treap中的排名
inline void merge (int &x,int l,int r){
//合并,x是传参
if (!l||!r){ //不存在qwq
x = l+r;
return;
}
if (tree[l].rand <= tree[r].rand){ //维护平衡树性质
x = l;merge (tree[x].son[1],tree[x].son[1],r);
push_up(x);return;
}
x = r;merge (tree[x].son[0],l,tree[x].son[0]);
push_up(x); //记得向上更新
}
inline void split (int x,int &l,int &r,int k){
//分裂,l,r是传参
if (!k){ //k=0是全部在右子树
l = 0;r = x;
return;
}
if (k == tree[x].size){ //全部在左子树
l = x;r = 0;
return;
}
if (k > tree[tree[x].son[0]].size){
l = x;split (tree[x].son[1],tree[x].son[1],r,k-tree[tree[x].son[0]].size-1);
push_up(x);return;
}
r = x;split (tree[x].son[0],l,tree[x].son[0],k);
push_up(x);
}
inline void new_node (int &x,int now){
tree[x = ++cnt].rand = rand ();
tree[x].v = now;
tree[x].size = 1;
} //建新节点
inline void insert (int now){
int x,y,z;
int rk = rank(root,now);
split (root,x,y,rk);
new_node (z,now);
merge (x,x,z);
merge (root,x,y);
} //分开,插入,合并
inline int prev (int now){
int x,y,z;
int rk = rank (root,now);
split (root,x,y,rk);
split (x,x,z,rk-1);
int ans = tree[z].v;
merge (x,x,z);
merge (root,x,y);
return ans;
} //找前驱
inline int succ (int now){
int x,y,z;
int rk = rank (root,now+1);
split (root,x,y,rk+1);
split (x,x,z,rk);
int ans = tree[z].v;
merge (x,x,z);
merge (root,x,y);
return ans;
} //跟找前驱一样,找后继
inline void del (int now){
int x,y,z;
int rk = rank (root,now)+1;
split (root,x,y,rk);
split (x,x,z,rk-1);
merge (root,x,y);
} //删节点,分裂再合并
int main(){
n = read (),q = read ();
//insert (-INF);
//insert (INF);
//这样会错。。。
insert (0);
insert (n+1);
while (q--){
//stack S;
//这样也会错。。。
char s[5];
int x;
scanf("%s",&s);
if (s[0] == 'D'){
x = read ();
Destroyed[x]=true;
insert (x);
//S.push (x);
stack[++top] = x;
//再也不用stl的stack了,效率慢还报错
}
if (s[0] == 'R'){
//x = S.top ();
//S.pop ();
x = stack[top--];
del (x);
Destroyed[x]=false;
}
if (s[0] == 'Q'){
x = read ();
if (Destroyed[x])puts("0");
//被炸得稀巴烂,还玩蛇皮
else printf("%d\n",succ(x)-prev(x)-1);
//后继减前驱,插了哨兵再减1
}
}
return 0;
}