【算法竞赛学习笔记】莫队算法-超优雅的暴力算法
title : 莫隊算法
tags : ACM,暴力
date : 2021-10-30
author : Linno
普通莫隊
常用操作:分塊/排序/卡常/離散化等,直接上板子。
luoguP2709 小B的詢問
#include<bits/stdc++.h> #define inf 0x3f3f3f3f //#define int long long using namespace std; const int N=5e4+7;//int read(){ int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=f*-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}struct X{int l,r,id; }q[N];int n,m,k,cnt[N],a[N],bel[N],ans[N],nowans,block;bool cmp(X a,X b){if(bel[a.l]!=bel[b.l]) return a.l<b.l;return a.r<b.r; }void add(int x){nowans-=cnt[a[x]]*cnt[a[x]];++cnt[a[x]];nowans+=cnt[a[x]]*cnt[a[x]]; }void del(int x){nowans-=cnt[a[x]]*cnt[a[x]];--cnt[a[x]];nowans+=cnt[a[x]]*cnt[a[x]]; }signed main(){cin>>n>>m>>k;block=sqrt(n);for(int i=1;i<=n;i++){cin>>a[i];bel[i]=i/block;}for(int i=1;i<=m;i++){cin>>q[i].l>>q[i].r;q[i].id=i;}sort(q+1,q+1+m,cmp);int L=0,R=-1;nowans=0; for(int i=1;i<=m;i++){while(L>q[i].l) add(--L);while(R<q[i].r) add(++R);while(L<q[i].l) del(L++);while(R>q[i].r) del(R--);ans[q[i].id]=nowans;}for(int i=1;i<=m;i++) cout<<ans[i]<<"\n";return 0; }帶修改莫隊
莫隊不能處理強制在線的題目,但是對于允許離線的修改區間查詢來說,依然能夠解決。給一個時間戳來記錄修改操作的編號,**而查詢操作的時間戳沿用最近一次修改操作的時間戳。**對于每次修改處理,只要再跑一次L,R指針就可以了。
因為加了一個時間的維度,我們需要重新分塊使得復雜度達到最優,塊的大小設為n23\sqrt[3]{n^2}3n2?,而不是n\sqrt nn?,否則復雜度會卡成n2n^2n2。
luoguP1903 [國家集訓隊]數顏色 / 維護隊列
#include<bits/stdc++.h> //#define int long long using namespace std; const int maxn=2e6+7; const int mod=1e9+7;inline int read(){ int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=f*-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}struct Q{int l,r,id,t; }q[maxn];struct Change{int pos,val; }C[maxn];int n,m,bl,idx,nowans,tot; int a[maxn],cnt[maxn],ans[maxn]; int L=1,R=0,now=0; char op[5];bool cmp(Q a,Q b){if(a.l/bl!=b.l/bl) return a.l<b.l; //相同桶按右端點小 if(a.r/bl!=b.r/bl) return a.r<b.r;return a.t<b.t; //左端點按桶 }inline void add(int val){ if(++cnt[val]==1) nowans++;}inline void del(int val){ if(--cnt[val]==0) nowans--;}void work(int now,int i){if(q[i].l<=C[now].pos&&C[now].pos<=q[i].r){if(--cnt[a[C[now].pos]]==0) nowans--;if(++cnt[C[now].val]==1) nowans++;}a[C[now].pos]=C[now].val; }inline void modify(int i){while(now<q[i].t){now++;if(q[i].l<=C[now].pos&&C[now].pos<=q[i].r){del(a[C[now].pos]);add(C[now].val);}swap(a[C[now].pos],C[now].val);}while(now>q[i].t){if(q[i].l<=C[now].pos&&C[now].pos<=q[i].r){del(a[C[now].pos]);add(C[now].val);}swap(a[C[now].pos],C[now].val);now--;} }signed main(){n=read();m=read();bl=pow(n,0.666);for(int i=1;i<=n;i++){a[i]=read();}for(int i=1;i<=m;i++){scanf("%s",op);if(op[0]=='R'){C[++idx].pos=read();C[idx].val=read();}if(op[0]=='Q'){q[++tot].l=read();q[tot].r=read();q[tot].id=tot;q[tot].t=idx;}}sort(q+1,q+1+tot,cmp);for(int i=1;i<=tot;i++){while(L<q[i].l) del(a[L++]);while(R<q[i].r) add(a[++R]);while(L>q[i].l) add(a[--L]);while(R>q[i].r) del(a[R--]);if(now!=q[i].t) modify(i);ans[q[i].id]=nowans;}for(int i=1;i<=tot;i++){printf("%lld\n",ans[i]);}return 0; }樹上莫隊
在原樹上跑一遍dfs序,那么一個子樹上的點就是一個連續的區間。
如果跑一邊歐拉序,那么一條路徑上經過的所有點就在一個連續的區間,并且額外加上他們的LCA。
SP10707 COT2 - Count on a tree II
#include <cstdio> #include <cstring> #include <cmath> #include <algorithm> using namespace std; #define maxn 200200 #define isdigit(x) ((x) >= '0' && (x) <= '9') inline int read() {int res = 0;char c = getchar();while(!isdigit(c)) c = getchar();while(isdigit(c)) res = (res << 1) + (res << 3) + (c ^ 48), c = getchar();return res; } int aa[maxn], cnt[maxn], first[maxn], last[maxn], ans[maxn], belong[maxn], inp[maxn], vis[maxn], ncnt, l = 1, r, now, size, bnum; //莫隊相關 int ord[maxn], val[maxn], head[maxn], depth[maxn], fa[maxn][30], ecnt; int n, m; struct edge {int to, next; } e[maxn]; void adde(int u, int v) {e[++ecnt] = (edge){v, head[u]};head[u] = ecnt;e[++ecnt] = (edge){u, head[v]};head[v] = ecnt; } void dfs(int x) {ord[++ncnt] = x;first[x] = ncnt;for(int k = head[x]; k; k = e[k].next) {int to = e[k].to;if(to == fa[x][0]) continue;depth[to] = depth[x] + 1;fa[to][0] = x;for(int i = 1; (1 << i) <= depth[to]; ++i) fa[to][i] = fa[fa[to][i - 1]][i - 1];dfs(to);}ord[++ncnt] = x;last[x] = ncnt; } int getlca(int u, int v) {if(depth[u] < depth[v]) swap(u, v);for(int i = 20; i + 1; --i) if(depth[u] - (1 << i) >= depth[v]) u = fa[u][i];if(u == v) return u;for(int i = 20; i + 1; --i)if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];return fa[u][0]; } struct query {int l, r, lca, id; } q[maxn]; int cmp(query a, query b) {return (belong[a.l] ^ belong[b.l]) ? (belong[a.l] < belong[b.l]) : ((belong[a.l] & 1) ? a.r < b.r : a.r > b.r); } void work(int pos) {vis[pos] ? now -= !--cnt[val[pos]] : now += !cnt[val[pos]]++;vis[pos] ^= 1; } int main() {n = read(); m = read();for(int i = 1; i <= n; ++i) val[i] = inp[i] = read();sort(inp + 1, inp + n + 1);int tot = unique(inp + 1, inp + n + 1) - inp - 1;for(int i = 1; i <= n; ++i)val[i] = lower_bound(inp + 1, inp + tot + 1, val[i]) - inp;for(int i = 1; i < n; ++i) adde(read(), read());depth[1] = 1;dfs(1);size = sqrt(ncnt), bnum = ceil((double) ncnt / size);for(int i = 1; i <= bnum; ++i)for(int j = size * (i - 1) + 1; j <= i * size; ++j) belong[j] = i;for(int i = 1; i <= m; ++i) {int L = read(), R = read(), lca = getlca(L, R);if(first[L] > first[R]) swap(L, R);if(L == lca) {q[i].l = first[L];q[i].r = first[R];}else {q[i].l = last[L];q[i].r = first[R];q[i].lca = lca;}q[i].id = i;}sort(q + 1, q + m + 1, cmp);for(int i = 1; i <= m; ++i) {int ql = q[i].l, qr = q[i].r, lca = q[i].lca;while(l < ql) work(ord[l++]);while(l > ql) work(ord[--l]);while(r < qr) work(ord[++r]);while(r > qr) work(ord[r--]);if(lca) work(lca);ans[q[i].id] = now;if(lca) work(lca);}for(int i = 1; i <= m; ++i) printf("%d\n", ans[i]);return 0; }回滾莫隊
適用范圍
詢問離線、不帶修改
區間伸長時易維護信息,區間縮短時不易維護。
只加不減的回滾莫隊
(1)對原序列進行分塊,并對詢問按照如下的方式排序:以左端點所在的塊升序為第一關鍵字,以右端點升序為第二關鍵字
(2)對于處理所有左端點在塊TT內的詢問,我們先將莫隊區間左端點初始化為R[T]+1,右端點初始化為R[T],這是一個空區間
(3)對于左右端點在同一個塊中的詢問,我們直接暴力掃描回答即可。
(4)對于左右端點不在同一個塊中的所有詢問,由于其右端點升序,我們對右端點只做加點操作,總共最多加點n次
(5)對于左右端點不在同一個塊中的所有詢問,其左端點是可能亂序的,我們每一次從R[T]+1的位置出發,只做加點操作,到達詢問位置即可,每一個詢問最多加√n次。回答完詢問后,我們撤銷本次移動左端點的所有改動,使左端點回到R[T]+1的位置
(6)按照相同的方式處理下一塊
只減不加的回滾莫隊
以左端點所在的塊升序為第一關鍵字,右端點降序為第二關鍵字
回答完詢問后,撤銷本次移動左端點的所有改動,使左端點回到L[T]的位置。
AT1219 歴史の研究
#include <bits/stdc++.h> #define int long long using namespace std; const int N = 1e5+20 , SIZE = 1020; int n,m,size,T,raw[N],val[N],t,cnt[N],cnt_[N]; int bel[N],L[SIZE],R[SIZE]; long long ans[N],Max,a[N]; struct query{int l,r,id;}q[N];inline void discrete(){ //離散化 sort(raw+1,raw+t+1);t=unique(raw+1,raw+t+1)-(raw+1);for (int i=1;i<=n;i++) val[i]=lower_bound(raw+1,raw+t+1,a[i])-raw; }inline void setblocks(){ //分塊 size=sqrt(n),T=n/size;for (int i=1;i<=T;i++){if (i*size>n) break;L[i] = (i-1) * size + 1;R[i] = i * size;}if (R[T]<n) T++,L[T]=R[T-1]+1,R[T]=n;for (int i=1;i<=T;i++)for (int j=L[i];j<=R[i];j++)bel[j]=i; }inline bool cmp(query p1,query p2){if (bel[p1.l]!=bel[p2.l])return bel[p1.l]<bel[p2.l];else return p1.r<p2.r; } // 加點 inline void insert(int p,long long &Maxval) {cnt[val[p]]++;Maxval = max( Maxval ,cnt[val[p]] * a[p] ); } // 撤銷 inline void resume(int p) {cnt[val[p]]--; }inline void CaptainMo(){sort(q+1,q+m+1,cmp);int l=1,r=0,lastblock=0;for (int i=1;i<=m;i++){ if (bel[q[i].l]==bel[q[i].r]){ // 處理同一塊中的詢問for (int j=q[i].l;j<=q[i].r;j++) cnt_[val[j]]++;int temp=0;for (int j=q[i].l;j<=q[i].r;j++) temp = max( temp ,cnt_[val[j]] * a[j] );for (int j=q[i].l;j<=q[i].r;j++) cnt_[val[j]]--;ans[ q[i].id ] = temp; continue; }// 如果移動到了一個新的塊,就先把左右端點初始化if (lastblock!=bel[q[i].l]){while ( r > R[bel[q[i].l]] ) resume(r--);while ( l < R[bel[q[i].l]]+1 ) resume(l++);Max = 0 , lastblock = bel[q[i].l];}// 單調地移動右端點while (r<q[i].r) insert(++r,Max);// 移動左端點回答詢問int temp = Max; int l_ = l;while (l_>q[i].l) insert(--l_,temp);// 回滾while (l_< l) resume(l_++);ans[ q[i].id ] = temp;} } signed main(){cin>>n>>m;for(int i=1;i<=n;i++){cin>>a[i];raw[++t]=a[i];}for(int i=1;i<=m;i++){cin>>q[i].l>>q[i].r;q[i].id=i;}discrete();setblocks();CaptainMo();for (int i=1;i<=m;i++)printf("%lld\n",ans[i]);return 0; }參考資料
https://www.cnblogs.com/Parsnip/p/10969989.html
https://www.cnblogs.com/WAMonster/p/10118934.html
總結
以上是生活随笔為你收集整理的【算法竞赛学习笔记】莫队算法-超优雅的暴力算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机内码汉字,gbk内码(汉字机内码在
- 下一篇: http://www.cnblogs.c