【題解】[TJOI2018]數學計算
阿新 • • 發佈:2021-06-22
\(\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; }