【题解】[TJOI2018]数学计算
\(\text{Solution:}\)
首先发现模数不是质数,这意味着没有逆元可以让我们把除操作变成乘操作。而\(x\)本身又没有去取模,所以我们应该考虑维护一段连续区间的乘积。
那么删除操作就变成了将某个之前的节点删除。这对于 fhq_treap 是小意思了。
由于是一段有序的操作区间,所以我们可以按照 siz 分裂,维护区间乘积。
注意乘积的维护,左右孩子只有有的时候才乘,并且每次应该先让\(\text{mul[x]=val[x]}\)这样可以避免重复乘。
注意代码中的 getpos 函数,这里是从之前 书架 那题学来的套路:维护一个操作对应的树中编号,通过记录父亲以获得其与其左边的节点个数。
这样我们就可以实现分裂操作了。维护乘积的细节和 getpos 函数的细节(加黑的地方是我忽略的地方)是收获所在。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+10;
int tr[MAXN][2],cv[MAXN],val[MAXN],mul[MAXN],cnt,siz[MAXN],pa[MAXN];
int Q,M,rt,pos[MAXN];
inline int rd() {
return rand()<<15|rand();
}
inline int md(int v) {
val[++cnt]=v;
cv[cnt]=rd();
mul[cnt]=v;
siz[cnt]=1;
return cnt;
}
inline void pushup(int x) {
siz[x]=siz[tr[x][0]]+siz[tr[x][1]]+1;
mul[x]=val[x];
if(tr[x][0])mul[x]=1ll*mul[x]*mul[tr[x][0]]%M;
if(tr[x][1])mul[x]=1ll*mul[x]*mul[tr[x][1]]%M;
if(tr[x][0])pa[tr[x][0]]=x;
if(tr[x][1])pa[tr[x][1]]=x;
}
void split(int now,int k,int &x,int &y) {
if(!now) {
x=y=0;
return;
}
if(k<=siz[tr[now][0]])y=now,split(tr[now][0],k,x,tr[now][0]);
else x=now,split(tr[now][1],k-siz[tr[now][0]]-1,tr[now][1],y);
pushup(now);
}
int merge(int x,int y) {
if(!x||!y)return x+y;
if(cv[x]<cv[y]) {
tr[x][1]=merge(tr[x][1],y);
pushup(x);
return x;
} else {
tr[y][0]=merge(x,tr[y][0]);
pushup(y);
return y;
}
}
int getpos(int x) {
int res=siz[tr[x][0]]+1;
while(pa[x]) {
if(x==tr[pa[x]][1])res+=siz[tr[pa[x]][0]]+1;
x=pa[x];
}
return res;
}
int T;
void del(int p){
int u=getpos(p);
int x,y,z;
split(rt,u-1,x,z);
split(z,1,y,z);
y=merge(tr[y][0],tr[y][1]);
rt=merge(merge(x,y),z);
printf("%d\n",mul[rt]);
}
int main() {
scanf("%d",&T);
while(T--) {
scanf("%d%d",&Q,&M);
int P=0;
rt=merge(rt,md(1));
pos[P]=cnt;
while(Q--) {
P++;
int opt,g;
scanf("%d%d",&opt,&g);
if(opt==1) {
rt=merge(rt,md(g));
pos[P]=cnt;
printf("%d\n",mul[rt]);
} else {
int node=pos[g];
del(node);
}
}
rt=0;
for(int i=1;i<=cnt;++i)tr[i][1]=tr[i][0]=pa[i]=siz[i]=val[i]=mul[i]=cv[i]=0;
for(int i=1;i<=P;++i)pos[i]=0;
cnt=0;
}
return 0;
}