BZOJ 1014: [JSOI2008]火星人prefix
阿新 • • 發佈:2018-12-13
如果沒有修改的操作,很容易想到 字尾陣列 倍增+雜湊求 LCQ
如果有修改呢,雜湊值就會發生改變,這時我們就要找一種資料結構來維護雜湊值
emm...改字元和插入字元....
顯然可以用平衡樹維護
所以總體思路就是用平衡樹維護雜湊值,然後倍增+雜湊求LCQ
怎麼維護雜湊值很容易想到,直接看具體程式碼好了
inline void pushup(int x) { int &l=ch[x][0],&r=ch[x][1]; sz[x]=sz[l]+sz[r]+1; t[x]=t[r]+pw[sz[r]]*val[x]+t[l]*pw[sz[r]+1平衡樹更新節點操作];//t存雜湊值 //pw存底數的倍數 }
提取一段區間的雜湊值也不難,把左邊界節點-1旋到根,右邊界節點+1旋到根的右兒子
那麼雜湊值就是根的右兒子的左兒子的雜湊值
雜湊直接用自然溢位雜湊就好了,(據說BZOJ卡常數?)
這題開始我是想二分寫的,然後發現很多細節要搞
最後改成倍增就好寫了
其他都是平衡樹的基本操作了
因為我們可能會訪問到1節點-1和n節點+1,所以要多插入兩個虛節點 0 號和 n+1 號
具體實現時一定要記得有多兩個節點
一開始直接建一顆標準的平衡樹就好了
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> using namespace std; typedef unsigned long long ull; inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int N=2e5+7,base=233; ull pw[N],t[N];//自然溢位 int sz[N],fa[N],ch[N][2],val[N],rt,cnt; inline void pushup(int x)//維護雜湊值和子樹大小 { int &l=ch[x][0],&r=ch[x][1]; sz[x]=sz[l]+sz[r]+1; t[x]=t[r]+pw[sz[r]]*val[x]+t[l]*pw[sz[r]+1]; } inline void rotate(int x,int &k) { int y=fa[x],z=fa[y],d=(ch[y][1]==x); if(y!=k) ch[z][(ch[z][1])==y]=x; else k=x; fa[x]=z; fa[y]=x; fa[ch[x][d^1]]=y; ch[y][d]=ch[x][d^1]; ch[x][d^1]=y; pushup(y); pushup(x); } inline void Splay(int x,int &k) { while(x!=k) { int y=fa[x],z=fa[y]; if(y!=k) { if(ch[y][1]==x ^ ch[z][1]==y) rotate(x,k); else rotate(y,k); } rotate(x,k); } } inline int find(int k)//找到區間中第k名的節點並返回它的編號 { int now=rt; while(1) { if(ch[now][0]&&k<=sz[ch[now][0]]) { now=ch[now][0]; continue; } if(k>sz[ ch[now][0] ]+1) k-=sz[ ch[now][0] ]+1,now=ch[now][1]; else return now; } } inline int get_hash(int l,int r)//從樹中取出閉區間[l,r]雜湊值 { int x=find(r+1); Splay(find(l-1),rt); Splay(x,ch[rt][1]); return t[ch[x][0]]; } inline int Q(int x,int y)//倍增求LCQ { int res=0; if(x<y) swap(x,y); for(int i=17;i>=0;i--) { if(x+(1<<i)-1>=sz[rt]) continue;//注意-1和>=,>=是因為有虛節點在最後 if( get_hash(x,x+(1<<i)-1)!=get_hash(y,y+(1<<i)-1) ) continue;//注意減1,閉區間 res|=(1<<i); x+=1<<i; y+=1<<i;//此處不用減1 } return res; } inline void change(int k,int c)//修改操作 { int x=find(k);//找到位置 val[x]=c; Splay(x,rt);//修改後記得Splay一波來調整整顆樹的形態和資料 } inline void ins(int k,int c)//插入操作 { int x=find(k);//先找到第k的節點 if(!ch[x][1]) ch[x][1]=++cnt;//注意特判 else//找它右邊子樹內最左的節點x,插入的位置就是x的左兒子 { x=ch[x][1]; while(ch[x][0]) x=ch[x][0]; ch[x][0]=++cnt; } fa[cnt]=x; val[cnt]=c; sz[cnt]=1; Splay(cnt,rt);//記得Splay一波來調整整顆樹的形態和資料 } inline void build(int l,int r,int f)//一開始先建一顆標準平衡樹 { int mid=l+r>>1; fa[mid]=f; ch[f][mid>f]=mid; if(mid>l) build(l,mid-1,mid); if(mid<r) build(mid+1,r,mid); pushup(mid); } int n,m; char s[N]; int main() { scanf("%s",s); cnt=n=strlen(s)+2;//注意此時cnt=n m=read(); pw[0]=1; for(int i=1;i<N;i++) pw[i]=pw[i-1]*base;//初始化pw for(int i=2;i<n;i++) val[i]=s[i-2]-'a',sz[i]=1; sz[1]=sz[n]=1; val[1]=val[n]=27;//兩個虛節點 rt=1+n>>1; build(1,n,0); char ss[5],c[5]; int a,b; while(m--) { scanf("%s",ss); if(ss[0]=='Q') { a=read(); b=read(); printf("%d\n",Q(a+1,b+1));//記得+1 continue; } a=read(); scanf("%s",c); if(ss[0]=='R') change(a+1,c[0]-'a');//a+1 else ins(a+1,c[0]-'a');//a+1 } return 0; }