【洛谷5113】Sabbat of the witch(毒瘤分塊)
大致題意: 給你一個序列,要你支援三種操作:區間賦值,區間求和,撤回之前任一區間賦值操作。
分塊
這道題應該是一道十分毒瘤的分塊題。
這道題要用到的演算法並不是很難,但是思維難度是真的高。
大致思路就是對於每一個塊維護一大堆資訊,各用一個棧儲存對每個塊的賦值操作,並開一個鄰接表來儲存對每個元素的賦值操作,防止\(MLE\)。
具體的操作方法見下。
區間賦值
對於不完整的塊,我們用鄰接表來更新這些元素的賦值操作,然後重構塊(關於重構詳見後文)。
對於完整的塊,我們用鄰接表來更新這個塊的賦值操作,然後更新這個塊的\(ans\)為塊的大小\(*\)賦的值。
區間求和
對於不完整的塊,我們暴力求解其答案。對於每一個元素要進行如下分類討論:
- 如果該塊沒有被整塊賦值過。則對於每個元素,我們判斷其是否被賦值過,如果賦值過,將\(res\)加上最後一次賦的值,否則將\(res\)加上該元素的初始值。
- 如果該塊被整塊賦值過。則對於每個元素,我們選擇該塊最後一次被賦值與該元素最後一次被賦值這兩種情況中較後一次所賦的值,然後將\(res\)加上這個值。
對於完整的塊,我們直接加上該塊的\(ans\)(\(ans\)會在每次操作中更新,因此一定是當前的答案)。
撤銷區間賦值
這應該是最噁心的一個操作了。
我們可以用一個\(flag\)陣列來標記每一個操作是否被撤銷。
考慮每次撤銷操作時,如果該元素/塊在這一次操作之後還有未被撤銷的賦值操作,則此次撤銷其實對答案暫時是沒有任何影響的。
因此,對於不完整的塊,我們只要將每個元素鄰接表中最後面的一些已被撤銷的操作全部清空,然後重構塊即可(關於重構詳見後文)。
而對於完整的塊,我們首先依然是將該塊的棧頂已被撤銷的操作全部清空,然後要依據重構塊時所保留下來的一些資訊來對\(ans\)進行修改。
由於重構的部分還未介紹,因此這一部分也放後面講。
重構
塊的重構應該也是這道題比較噁心的地方之一。
首先,我們要對塊內每一個元素最後一次被賦值的時間進行一次基數排序。
這樣一來,這些元素就按照最後一次被賦值的時間從小到大被排好序了。
然後,我們倒著對被賦的值求一次字尾和,用一個數組\(res\)來儲存。
千萬要注意的是,\(res_i\)儲存的是從第\(i+1\)個元素開始的字尾和。
還有,一定要記得將\(res_0\)加上所有未被修改過的元素的初始值。
接下來,我們要使用兩個變數\(lst\)和\(p\),它們在後面的塊內撤銷操作中也會發揮作用。
我們用\(lst\)來記錄當前堆頂的元素值,它的實際意義即為最後一次整塊修改的時間。
然後,我們用\(p\)從\(0\)開始列舉,找到最後一個單個元素賦值時間小於\(lst\)的元素。
則顯然,第\(1\sim p\)個元素的值都應是整塊修改所賦的值,而第\(p+1\sim Size\)個元素都是按自己最後一次賦值所賦的值。
不難發現,前面一部分即為\(p*\)整塊最後一次修改所賦的值,後面一部分即為\(res_p\)。
這樣就能更新\(ans\)了。
塊內撤銷操作
首先我們要注意到一個性質:在每次重構之前,所撤銷的操作的編號肯定是單調遞減的。
因此我們就可以使用之前重構時保留下來的\(lst\)和\(p\)兩個變數,繼續進行操作。
我們先判斷當前棧頂的元素是否大於\(lst\)。
如果大於\(lst\),則說明在重構之後又進行過整塊修改,而這次整塊修改肯定是覆蓋塊內所有元素的。
所以可以直接更新\(ans\)為塊的大小\(*\)賦的值,然後退出函式。
否則,我們就要將\(p\)向前移,和之前的操作差不多剛好相反,找到最後一個單個元素賦值時間小於當前堆頂的元素。
然後按照上面的方法,更新\(ans\)為\(p*\)整塊最後一次修改所賦的值\(+res_p\)。
程式碼
#include<bits/stdc++.h>
#define N 100000
#define SqrtN 400
#define LL long long
using namespace std;
LL n,a[N+5],ql[N+5],qr[N+5],qv[N+5],flag[N+5];
class Class_FIO
{
private:
#define Fsize 100000
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,Fsize,stdin),A==B)?EOF:*A++)
#define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,Fsize,stdout),Fout[(FoutSize=0)++]=ch))
#define Ftemp template<typename I>
int Top,FoutSize;char ch,*A,*B,Fin[Fsize],Fout[Fsize],Stack[Fsize];
public:
Class_FIO() {A=B=Fin;}
Ftemp inline void read(I &x) {x=0;while(!isdigit(ch=tc()));while(x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));}
Ftemp inline void writeln(I x) {while(Stack[++Top]=x%10+48,x/=10);while(Top) pc(Stack[Top--]);pc('\n');}
inline void clear() {fwrite(Fout,1,FoutSize,stdout),FoutSize=0;}
}F;
class Class_List//鄰接表,儲存對每個元素的賦值操作
{
private:
#define LIST_SIZE N*SqrtN
LL cnt,lnk[N+5],val[LIST_SIZE+5],nxt[LIST_SIZE+5];
public:
inline void Add(LL x,LL v) {nxt[++cnt]=lnk[x],val[lnk[x]=cnt]=v;}
inline void Del(LL x) {while(lnk[x]&&flag[val[lnk[x]]]) lnk[x]=nxt[lnk[x]];}
inline LL GetVal(LL x) {return val[lnk[x]];}
}List;
class Class_BlockSolver//儲存每個塊的資訊
{
private:
#define BLOCK_SIZE SqrtN
LL Bsize,Btot,bl[N+5];
class Class_Block
{
private:
#define BLOCK SqrtN
LL p,ans,lst,Top,res[BLOCK+5],InitVal[BLOCK+5],s[BLOCK+5],g[BLOCK+5],cnt[256],Stack[N+5];
inline LL RadixSort()//基數排序
{
register LL i,res=0;
for(i=1;i<=S;++i) !(s[i]=List.GetVal(L+i-1))&&(res+=InitVal[i]);
for(i=0;i<256;++i) cnt[i]=0;for(i=1;i<=S;++i) ++cnt[s[i]&255];for(i=1;i<256;++i) cnt[i]+=cnt[i-1];for(i=S;i;--i) g[cnt[s[i]&255]--]=s[i];
for(i=0;i<256;++i) cnt[i]=0;for(i=1;i<=S;++i) ++cnt[s[i]>>8];for(i=1;i<256;++i) cnt[i]+=cnt[i-1];for(i=S;i;--i) s[cnt[g[i]>>8]--]=g[i];
return res;
}
public:
LL L,R,S;
inline void Build(LL *data) {for(register LL i=1;i<=S;++i) res[0]+=(InitVal[i]=a[L+i-1]);ans=res[0];}//初始化
inline void ReBuild()//重構
{
register LL i,sum=RadixSort();
for(p=0,lst=Stack[Top],i=S-1;~i;--i) res[i]=res[i+1]+qv[s[i+1]];res[0]+=sum;
while(p<S&&s[p+1]<lst) ++p;ans=res[p]+1LL*p*qv[lst];
}
inline void Add(LL pos) {ans=1LL*S*qv[Stack[++Top]=pos];}//整塊修改
inline void Del()//整塊撤銷操作
{
while(Top&&flag[Stack[Top]]) --Top;
if(Stack[Top]>lst) return (void)(ans=1LL*S*qv[Stack[Top]]);
while(p&&s[p]>=Stack[Top]) --p;ans=res[p]+1LL*p*qv[Stack[Top]];
}
inline LL BruteForce(LL l,LL r)//暴力求解答案
{
register LL i,res=0;
if(Stack[Top]) for(i=l;i<=r;++i) res+=qv[max(List.GetVal(i),Stack[Top])];
else for(i=l;i<=r;++i) res+=List.GetVal(i)?qv[List.GetVal(i)]:InitVal[i-L+1];
return res;
}
inline LL GetAns() {return ans;}//返回答案
}blk[BLOCK_SIZE+5];
public:
inline void Init()//初始化
{
register LL i;
for(Bsize=sqrt(n),i=1;i<=n;++i) ++blk[bl[i]=(i-1)/Bsize+1].S;
for(Btot=bl[n],i=1;i<=Btot;++i) blk[i].L=(i-1)*Bsize+1,blk[i].R=i*Bsize;blk[Btot].R=n;
for(i=1;i<=Btot;++i) blk[i].Build(a);
}
inline void Modify(LL pos)//修改
{
register LL i,l=ql[pos],r=qr[pos],v=qv[pos];
if(!(bl[l]^bl[r])) {for(i=l;i<=r;++i) List.Add(i,pos);return blk[bl[l]].ReBuild();}
for(i=blk[bl[l]].R;i>=l;--i) List.Add(i,pos);blk[bl[l]].ReBuild();
for(i=blk[bl[r]].L;i<=r;++i) List.Add(i,pos);blk[bl[r]].ReBuild();
for(i=bl[l]+1;i<bl[r];++i) blk[i].Add(pos);
}
inline void CtrlZ(LL pos)//撤銷修改
{
register LL i,l=ql[pos],r=qr[pos],v=qv[pos];
if(!(bl[l]^bl[r])) {for(i=l;i<=r;++i) List.Del(i);return blk[bl[l]].ReBuild();}
for(i=blk[bl[l]].R;i>=l;--i) List.Del(i);blk[bl[l]].ReBuild();
for(i=blk[bl[r]].L;i<=r;++i) List.Del(i);blk[bl[r]].ReBuild();
for(i=bl[l]+1;i<bl[r];++i) blk[i].Del();
}
inline LL Query(LL l,LL r)//詢問
{
if(!(bl[l]^bl[r])) return blk[bl[l]].BruteForce(l,r);
register LL i,res=blk[bl[l]].BruteForce(l,blk[bl[l]].R)+blk[bl[r]].BruteForce(blk[bl[r]].L,r);
for(i=bl[l]+1;i<bl[r];++i) res+=blk[i].GetAns();
return res;
}
}B;
int main()
{
register LL query_tot,i,cnt=0,op,x,y,ans=0;
for(F.read(n),F.read(query_tot),i=1;i<=n;++i) F.read(a[i]);B.Init();
while(query_tot--)
{
F.read(op);switch(op)
{
case 1:F.read(ql[++cnt]),F.read(qr[cnt]),F.read(qv[cnt]),ql[cnt]^=ans,qr[cnt]^=ans,B.Modify(cnt);break;
case 2:F.read(x),F.read(y),F.writeln(ans=B.Query(x^ans,y^ans));break;
case 3:F.read(x),flag[x^=ans]=1,B.CtrlZ(x);break;
}
}
return F.clear(),0;
}