莫队学习笔记
莫队学习笔记
带修莫队
因为莫队与带修莫队差距不大,放在一块讲。
莫队是一个离线算法,如果你维护的信息可以\(O(1)\)的进行区间扩展,即从\([l,r]\)到\([l-1,r],[l,r+1],[l+1,r],[l,r-1]\)。那么他就可以用莫队算法解决,特殊地,对于修改也可以\(O(1)\)维护的,便可以使用带修莫队。
具体做法
将序列分块,每块大小为\(B\),我们将询问排序,以\(l/B\)为第一关键字,\(r/B\)为第二关键字进行排序。对于每个询问,我们暴力移动\(l,r\),并更新答案。
我们发现,对于\(l\)在同一块中的询问,\(l\)的移动复杂度是\(O(B)\)的,\(r\)的移动是单调的,复杂度上界为\(O(n)\)。
对于不在同一块之间的移动,我们称为"换块",“换块”一定是相邻两个块之间的,\(l\)的复杂度是\(O(B)\)的,\(r\)的复杂度是\(O(n)\)的,
因为共有\((n/B)\)个块,于是复杂度为\(O(nB+n^2/B)\) 当\(B = \sqrt n\)时最小。(严谨来讲,如果询问次数为\(m\),复杂度为\(O(mB+n^2/B)\),当\(B = \frac{n}{\sqrt m}\)时最优)。
对于带修莫队,以修改时间为第三关键字进行排序,类似可以分析得,取\(B = n^{\frac{2}{3}}\)时,复杂度最优,为\(O(n^{\frac{5}{3}})\)
例题
模板题,对于修改操作,按照是否在当前区间内分类即可。
\(code:\)
#include<bits/stdc++.h>
#define ll long long
#define N 133399
#define rep(i,a,n) for (int i=a;i<=n;i++)
#define per(i,a,n) for (int i=n;i>=a;i--)
#define inf 0x3f3f3f3f
#define pb push_back
#define mp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define lowbit(i) ((i)&(-i))
#define VI vector<int>
#define all(x) x.begin(),x.end()
#define SZ(x) ((int)x.size())
using namespace std;
int B;
int n,m,a[N],cnt[1000015],ans,res[N];
VI cur[N];
struct query{
int l,r,t,id;
bool operator <(const query &rhs) const{
if(l/B != rhs.l/B) return l/B < rhs.l/B;
if(r/B != rhs.r/B) return r/B < rhs.r/B;
return t < rhs.t;
}
}q[N];
int top;
vector<pii> C;
void add(int x){if(cnt[x]++ == 0) ans++;}
void del(int x){if(--cnt[x] == 0) ans--;}
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
scanf("%d%d",&n,&m);
C.pb(mp(0,0));
B = (int)pow(n,0.666);
rep(i,1,n) scanf("%d",&a[i]),cur[i].pb(a[i]);
rep(i,1,m){
char s[3]; int u,v; scanf("%s%d%d",s+1,&u,&v);
if(s[1] == 'Q') q[++top] = (query){u,v,SZ(C)-1,top};
else C.pb(mp(u,v));
}
// puts("Yes");
sort(q+1,q+top+1);
// cerr << "Yes" << endl;
for(int i = 1,l = 1,r = 0,t = 0;i <= top;++i){
while(t < q[i].t) { t++;
int u = C[t].fi,v = C[t].se;
if(l <= u && u <= r) del(a[u]),add(v);
a[u] = v; cur[u].pb(v);
}
while(t > q[i].t){
int u = C[t].fi; cur[u].pop_back();
int v = cur[u].back();
if(l <= u && u <= r) del(a[u]),add(v);
a[u] = v; t--;
}
while(l > q[i].l) add(a[--l]);
while(r < q[i].r) add(a[++r]);
while(l < q[i].l) del(a[l++]);
while(r > q[i].r) del(a[r--]);
res[q[i].id] = ans;
}
rep(i,1,top) printf("%d\n",res[i]);
return 0;
}
树上莫队
把树转成欧拉序,在序列上建莫队。设\(st[u]\)为入栈时间戳,\(ed[u]\)为出栈时间戳。
对于路径\((u,v)满足st[u]<st[v]\)。
- 如果\(u = lca(u,v)\),那么路径上的点为欧拉序中\(st[u],st[v]\)之间出现一次的点,其他均出现两次。
- 否则,那么路径上的点为欧拉序中\(ed[u],st[v]\)之间出现一次的点,其他均出现两次。
注意,第2种情况下,两点的\(lca\)并不会被考虑到,于是要单独加入删除\(lca\)。
例题
树上莫队维护每个数出现次数的奇偶性,在对值域分块,维护出每块中出现奇数次数的个数即可。
\(code:\)
#include<bits/stdc++.h>
#define ll long long
#define N 600015
#define B 776
#define rep(i,a,n) for (int i=a;i<=n;i++)
#define per(i,a,n) for (int i=n;i>=a;i--)
#define inf 0x3f3f3f3f
#define pb push_back
#define mp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define lowbit(i) ((i)&(-i))
#define VI vector<int>
#define all(x) x.begin(),x.end()
#define SZ(x) ((int)x.size())
using namespace std;
int n,m,a[N],cnt[N],ccnt[N];
VI e[N];
int st[N],ed[N],rev[N],clk;
int fa[N][25],dep[N];
struct query{
int l,r,L,R,lca,id;
bool operator <(const query& rhs) const{
if(l/B != rhs.l/B) return l/B < rhs.l/B;
if((l/B)&1) return r/B < rhs.r/B;
return r/B > rhs.r/B;
}
}q[N];
void dfs(int u,int f){
fa[u][0] = f; dep[u] = dep[f]+1; st[u] = ++clk; rev[clk] = u;
rep(i,1,19) fa[u][i] = fa[fa[u][i-1]][i-1];
for(auto v:e[u]) if(v != f){
dfs(v,u);
}
ed[u] = ++clk; rev[clk] = u;
}
int lca(int u,int v){
if(dep[u] < dep[v]) swap(u,v);
int d = dep[u]-dep[v];
rep(i,0,19) if((1<<i)&d) u = fa[u][i];
if(u == v) return u;
per(i,0,19){
if(fa[u][i] != fa[v][i]) u = fa[u][i],v = fa[v][i];
}
return fa[u][0];
}
void getq(){
rep(i,1,m){
int u,v,l,r; scanf("%d%d%d%d",&u,&v,&l,&r);
int LCA = lca(u,v);
q[i].id = i; q[i].L = l,q[i].R = r;
if(st[u] > st[v]) swap(u,v);
if(u == LCA) q[i].l = st[u],q[i].r = st[v];
else q[i].l = ed[u],q[i].r = st[v],q[i].lca = LCA;
}
sort(q+1,q+m+1);
}
int ans[N];
void add(int x){
if(cnt[x]) ccnt[x/B]--;
else ccnt[x/B]++;
cnt[x] ^= 1;
}
int getans(int l,int r){
rep(i,l/B+1,r/B-1){
if(!ccnt[i]) continue;
rep(j,i*B,i*B+B-1) if(cnt[j]) return j;
}
int R = l/B*B+B-1,L = r/B*B;
rep(i,l,min(r,R)) if(cnt[i]) return i;
rep(i,max(l,L),r) if(cnt[i]) return i;
return -1;
}
void solve(){
for(int i = 1,l = 1,r = 0;i <= m;++i){
while(l > q[i].l) add(a[rev[--l]]);
while(r < q[i].r) add(a[rev[++r]]);
while(l < q[i].l) add(a[rev[l++]]);
while(r > q[i].r) add(a[rev[r--]]);
if(q[i].lca) add(a[q[i].lca]);
ans[q[i].id] = getans(q[i].L,q[i].R);
if(q[i].lca) add(a[q[i].lca]);
}
rep(i,1,m) printf("%d\n",ans[i]);
}
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
scanf("%d%d",&n,&m);
rep(i,1,n) scanf("%d",&a[i]);
rep(i,2,n){ int u,v; scanf("%d%d",&u,&v);
e[u].pb(v); e[v].pb(u);
}
dfs(1,0);
getq();
solve();
return 0;
}