[資料結構] 莫隊
阿新 • • 發佈:2021-08-12
[資料結構] 莫隊
莫隊是一種基於分塊思想的離線演算法。
本文章介紹初等莫隊。
今天才學會帶修
普通莫隊
一個板子題,要求維護平方和。
直接給出公式吧:
\(ans=\frac{\sum_{i=1}^c x_i^2-(R-L+1)}{(R-L+1)(R-L)}\)
莫隊的基本思想
對左右端點進行分塊,左端點所在塊為第一關鍵字,右端點所在塊為第二關鍵字,這樣就保證了普通莫隊的複雜度是 \(\text O(n\sqrt{n})\) .
初始時 \(l=1,r=0\),意義是 \([l,r]\) 這段區間已經處理過了,然後根據詢問的左右端點不斷擴充套件或者撤銷。(具體用四個迴圈實現)
細節部分
-
特判 \(l=r\) 的情況。
-
防止死迴圈。
-
一定注意左右端點 \(l,r\) 的把控,細節想到位。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; template <typename T> inline T read(){ T x=0;char ch=getchar();bool fl=false; while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();} while(isdigit(ch)){ x=(x<<3)+(x<<1)+(ch^48);ch=getchar(); } return fl?-x:x; } const int maxn = 5e4 + 10; #define LL long long int c[maxn],t[maxn]; struct node{ int l,r,id; LL a,b; }a[maxn]; LL gcd(LL a,LL b){ if(!b)return a; return gcd(b,a%b); } int block[maxn],n1,n,m; #define read() read<int>() #include <cmath> inline bool cmp(node a,node b){ if(block[a.l]!=block[b.l])return a.l<b.l; return a.r<b.r; } inline bool cmp_id(node a,node b){ return a.id<b.id; } LL ans; inline void update(int p,int val){ ans-=t[c[p]]*t[c[p]]; t[c[p]]+=val; ans+=(LL)t[c[p]]*t[c[p]]; } void solve(){ for(int i=1,l=1,r=0;i<=m;i++){ while(r<a[i].r)update(r+1,1),r++; while(l>a[i].l)update(l-1,1),l--; while(r>a[i].r)update(r,-1),r--; while(l<a[i].l)update(l,-1),l++; if(a[i].l==a[i].r){ a[i].a=0;a[i].b=1;continue; } a[i].a=ans-(a[i].r-a[i].l+1); a[i].b=1LL*(a[i].r-a[i].l+1)*(a[i].r-a[i].l); LL g=gcd(a[i].a,a[i].b); a[i].a/=g;a[i].b/=g; } } int main(){ n=read();m=read();n1=(int)sqrt(n+0.5); for(int i=1;i<=n;i++)c[i]=read(); for(int i=1;i<=n;i++)block[i]=(i-1)/n1+1; for(int i=1;i<=m;i++){ a[i].l=read();a[i].r=read();a[i].id=i; } sort(a+1,a+1+m,cmp); solve(); sort(a+1,a+1+m,cmp_id); for(int i=1;i<=m;i++)printf("%lld/%lld\n",a[i].a,a[i].b); return 0; }
帶修改莫隊
與普通莫隊不同的是,帶修改莫隊會 在一個特定的時間點進行對資訊的修改 ,這就需要我們把二元組改為三元組 \((l,r,time)\) 。
其中時間按照第三關鍵字排序,塊長改為 \(n^{\frac{2}{3}}\)。
複雜度為 \(\text O(n^{\frac{5}{3}})\) 。
做法和普通莫隊的區別:
每次擴充套件區間的時候(不要以為時間是順序的),看當前需要修改的操作,如果這個操作在時間軸中包含於擴充套件的區間 \([l,r]\) ,看它和當前查詢操作 \(time\) 的關係,進行合理的 撤銷或修改。
需要自己慢慢體會。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; template <typename T> inline T read(){ T x=0;char ch=getchar();bool fl=false; while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();} while(isdigit(ch)){ x=(x<<3)+(x<<1)+(ch^48);ch=getchar(); } return fl?-x:x; } const int maxn = 1333333 + 10; #include <cmath> int n,m,c[maxn],ct[maxn]; int mem[maxn][3],c1,c2,tot[maxn]; int Ans[maxn],n1; struct node{ int l,r,t,id; bool operator <(const node &x)const{ if(l/n1 == x.l/n1){ if(r/n1 == x.r/n1) return t<x.t; return r<x.r; } return l<x.l; } }a[maxn]; int ans; #define read() read<int>() inline void del(int x){ if(--tot[x]==0)ans--; } inline void add(int x){ if(++tot[x]==1)ans++; } int main(){ n=read();m=read();n1=pow(n,0.666666); for(int i=1;i<=n;i++){ c[i]=read();ct[i]=c[i]; } for(int i=1;i<=m;i++){ char op;cin>>op;int x=read(),b=read(); if(op=='Q'){ c1++;a[c1]=(node){x,b,c2,c1}; } else { c2++;mem[c2][0]=x;mem[c2][1]=ct[x];mem[c2][2]=ct[x]=b;//鏇存敼浣嶇疆錛屽師鏉ワ紝鐜板湪棰滆壊 } } sort(a+1,a+1+c1); int l=1,r=0,last=1; for(int i=1;i<=c1;i++){ while(last<=a[i].t){ if(l<=mem[last][0] && mem[last][0]<=r) del(mem[last][1]),add(mem[last][2]); c[mem[last][0]]=mem[last][2]; last++; } while(last-1>a[i].t){ if(l<=mem[last-1][0] && mem[last-1][0]<=r) del(mem[last-1][2]),add(mem[last-1][1]); c[mem[last-1][0]]=mem[last-1][1]; last--; } while(r<a[i].r)add(c[r+1]),r++; while(l>a[i].l)add(c[l-1]),l--; while(r>a[i].r)del(c[r]),r--; while(l<a[i].l)del(c[l]),l++; Ans[a[i].id]=ans; } for(int i=1;i<=c1;i++)printf("%d\n",Ans[i]); return 0; }