經典題,國賽前才做怎麼回事。
一句話題意:末尾加刪,區間詢問凸包資訊。
一個做法是建出操作樹,發現本題相當於路徑查詢凸包資訊。於是可以樹剖/點分治。點分治的話可以轉化成只有字首詢問的情況用平衡樹維護圖報加入一個點和回退。但是這樣太難寫了!觀察到詢問只有直上直下的鏈(當然如果不是可以拆成兩條這樣的鏈)。我們點分治時特殊處理當前分治連通塊中最淺的點所在的子連通塊,發現所有經過中心的路徑在這個子連通塊中的部分都是根到重心的鏈,所以只需要求動態加入一個點的凸包可以 CDQ/平衡樹維護。路徑剩下的部分遞迴下去做就行了。演算法離線,空間複雜度 \(O(n)\)。
另一個做法線上,不過空間複雜度有一個 \(\log\)。考慮只加怎麼做,我們可以二進位制分組。這個題是區間詢問,所以得維護一個線段樹的結構,然後每次往後加,如果線段樹上節點加滿了就 pushup
求出這個節點的資訊,由於區間詢問不會訪問沒有加滿的點所以這樣做就是對的。
有刪除操作呢?我們思考二進位制分組的原理很類似一個二進位制計數器,每次給這個數 \(+1\) 暴力進位的攤還代價是 \(O(1)\)。現在有刪除操作相當於有 \(-1\) 操作。如果你在一個勢能(末尾的 \(1\) 的個數很多)的點反覆 \(\pm 1\) 複雜度就會爆炸。此時我們想到了我們解決整數一題中使用的 Trygub Number Trick。即在 \(B\) 進位制下允許一位的值可以是 \((-B,B)\) 中間的數。
Trygub Number 對應著這樣一種思想:允許少數地方在刪除若干次後保持這樣的不合法狀態。避免在加刪操作都有的時候一個勢能大的狀態“過於敏感”。即讓一個勢能大的狀態操作釋放勢能一次之後不會立馬操作到另一個大勢能狀態。(講得好抽象 QwQ)
這道題中我們可以這樣幹:允許每一層最後一個滿了的區間還沒有求出資訊,查到這樣不合法的節點時暴力遞迴下去,這樣查詢仍然只會訪問 \(\log\) 個節點。而每一層一旦發生了一次 pushup
就必須要再操作區間長度次才會發生另一次 pushup
。
這個技巧明顯很好擴充套件,比如雙端插入刪除也可以使用這個技巧。
兩個時間複雜度都是兩個 \(\log\),後者常數明顯更小,我實現了後者。(但是我寫的程式碼常數很大 QwQ)
#include <cstdio>
#include <algorithm>
#define lc (p<<1)
#define rc (p<<1|1)
using namespace std;
int read(){
char c=getchar();int x=0;bool f=0;
while(c<48||c>57) f|=(c=='-'),c=getchar();
do x=x*10+(c^48),c=getchar();
while(c>=48&&c<=57);
if(f) return -x;
return x;
}
typedef long long ll;
const int W=1<<20,N=500003,P=998244353;
const ll INF=0x3f3f3f3f3f3f3f3f;
struct node{
int x,y;
friend bool operator<(const node a,const node b){
if(a.x^b.x) return a.x<b.x;
return a.y<b.y;
}
};
bool ava[W];node *nd[W];
int dlt,bt,m,len;
int sz[W];
node arr[N];
node stk[N];int tp;
inline bool check(node a,node b,node c){
return (ll)(b.y-a.y)*(c.x-b.x)<=(ll)(c.y-b.y)*(b.x-a.x);
}
inline void pushup(int p){
if(ava[p]) return;
ava[p]=1;
merge(nd[lc],nd[lc]+sz[lc],nd[rc],nd[rc]+sz[rc],arr);
tp=0;
for(int i=0;i<sz[lc]+sz[rc];++i){
while(tp>1&&check(stk[tp-1],stk[tp],arr[i])) --tp;
stk[++tp]=arr[i];
}
nd[p]=new node[sz[p]=tp];
copy(stk+1,stk+tp+1,nd[p]);
}
ll query(int l,int r,int x,int y,int p=1,int d=0){
int L=(p<<(bt-d))-dlt;
int R=L+(1<<(bt-d))-1;
if(r<L||l>R) return -INF;
if(ava[p]&&l<=L&&R<=r){
int l=0,r=sz[p]-1;
while(l<r){
int md=(l+r)>>1;
if((ll)y*(nd[p][md+1].x-nd[p][md].x)<(ll)(nd[p][md+1].y-nd[p][md].y)*x)
l=md+1;
else r=md;
}
return (ll)x*nd[p][r].y-(ll)y*nd[p][r].x;
}
return max(query(l,r,x,y,rc,d+1),query(l,r,x,y,lc,d+1));
}
void solve(){
len=0;
for(dlt=1,bt=0;dlt<=m;dlt<<=1,++bt);
int res=0;
while(m--){
int op=read();
if(op==1){
int p=dlt+(len++),d=bt;
ava[p]=1;
nd[p]=new node[sz[p]=1];
nd[p]->x=read();
nd[p]->y=read();
while(p>1){
p>>=1;--d;
if(((p+1)<<(bt-d))>dlt+len||p==(1<<bt)) break;
pushup(p-1);
}
}
if(op==2){
int p=dlt+(--len);
ava[p]=0,delete[] nd[p];
while(p>1){
p>>=1;
if(ava[p]) ava[p]=0,delete[] nd[p];
}
}
if(op==3){
int l=read()-1,r=read()-1,x=read(),y=read();
res^=(query(l,r,x,y)%P+P)%P;
}
}
printf("%d\n",res);
for(int i=1;i<dlt*2;++i) if(ava[i]) ava[i]=0,delete[] nd[i];
}
int main(){
read();m=read();
while(m) solve(),m=read();
return 0;
}