莫队学习笔记

莫队学习笔记

带修莫队

因为莫队与带修莫队差距不大,放在一块讲。

莫队是一个离线算法,如果你维护的信息可以\(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]\)

  1. 如果\(u = lca(u,v)\),那么路径上的点为欧拉序中\(st[u],st[v]\)之间出现一次的点,其他均出现两次。
  2. 否则,那么路径上的点为欧拉序中\(ed[u],st[v]\)之间出现一次的点,其他均出现两次。

注意,第2种情况下,两点的\(lca\)并不会被考虑到,于是要单独加入删除\(lca\)

例题

CodeForces 1479D

树上莫队维护每个数出现次数的奇偶性,在对值域分块,维护出每块中出现奇数次数的个数即可。

\(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;
}
posted @ 2021-02-25 23:00  趁着胆子小  阅读(81)  评论(0编辑  收藏  举报
//explotion effect (unabled)