莫队进阶指南

目录

例1: BZOJ3289 Mato的文件管理

例2: WOJ4301Gty的二逼妹子序列

例3: P3246 [HNOI2016]序列

待修莫队

 P1903 [国家集训队]数颜色

树上莫队

例1:WOJ1196 苹果树 

例2:P4074 [WC2013]糖果公园


一般的莫队比较简单, 考虑从[l, r] 到 [l, r+1]的变化一般都能解决掉, 这里放3道例题

例1: BZOJ3289 Mato的文件管理

因为相邻的交换最多消除一个逆序对, 所以逆序对个数就是交换次数, 莫队套个树状数组就好了

#include<bits/stdc++.h>
#define N 500050
using namespace std;
typedef long long ll;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
int n, m, b[N], siz, a[N], pos[N], L[N], R[N], cnt; ll c[N], sum, ans[N];
struct Node{ int l, r, id;}q[N];
bool cmp(Node a, Node b){
	if(pos[a.l] == pos[b.l]) return a.r < b.r;
	return pos[a.l] < pos[b.l];
}
void Add(int x, int val){for(;x<=n;x+=x&-x) c[x] += val;}
ll Ask(int x){ll ans=0; for(;x;x-=x&-x) ans += c[x]; return ans;}
void suf_Add(int x){ sum += Ask(n) - Ask(x); Add(x, 1);}
void suf_Del(int x){ Add(x, -1); sum -= Ask(n) - Ask(x);}
void pre_Add(int x){ sum += Ask(x-1); Add(x, 1);}
void pre_Del(int x){ Add(x, -1); sum -= Ask(x-1);}
int main(){
	n = read();
	for(int i=1; i<=n; i++) a[i] = b[i] = read();
	sort(b+1, b+n+1); int siz = unique(b+1, b+n+1) - (b+1);
	for(int i=1; i<=n; i++) a[i] = lower_bound(b+1, b+siz+1, a[i]) - b;
	int S = sqrt(n); 
	for(int i=1; i<=n; i++) pos[i] = (i-1) / S + 1;
	m = read();
	for(int i=1; i<=m; i++) q[i] = (Node){read(), read(), i};
	sort(q+1, q+m+1, cmp);
	int l = 1, r = 0;
	for(int i=1; i<=m; i++){
		while(r < q[i].r) suf_Add(a[++r]); while(r > q[i].r) suf_Del(a[r--]);
		while(l < q[i].l) pre_Del(a[l++]); while(l > q[i].l) pre_Add(a[--l]);
		ans[q[i].id] = sum;
	} for(int i=1; i<=m; i++) printf("%lld\n", ans[i]);
	return 0;
}

例2: WOJ4301Gty的二逼妹子序列

比较直接的是用树状数组维护, 但考虑到O(1)修改, O(sqrt(n)) 查询会更加优秀, 所以用分块维护

#include<bits/stdc++.h>
#define N 1000050
using namespace std;
typedef long long ll;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
int n, m, siz, a[N], pos[N], L[N], R[N], ans[N], res; 
int val[N], sum[N], cnt[N];
struct Node{ int l, r, a, b, id;}q[N];
bool cmp(Node a, Node b){ 
	if(pos[a.l] == pos[b.l]) return a.r < b.r;
	return pos[a.l] < pos[b.l];
}
void Add(int x){ if(cnt[x] == 0) val[x]++, sum[pos[x]]++; cnt[x]++;}
void Del(int x){ if(cnt[x] == 1) val[x]--, sum[pos[x]]--; cnt[x]--;}
int Qu(int l, int r){
	int ans = 0;
	if(pos[r] - pos[l] <= 1){ 
		for(int i=l; i<=r; i++) ans += val[i];
	}
	else{
		for(int i=pos[l]+1; i<=pos[r]-1; i++) ans += sum[i];
		for(int i=l; i<=R[pos[l]]; i++) ans += val[i];
		for(int i=L[pos[r]]; i<=r; i++) ans += val[i];
	} return ans;
}
int main(){
	n = read(); m = read();
	for(int i=1; i<=n; i++) a[i] = read();
	
	int S = sqrt(n);
	for(int i=1; i<=n; i++) pos[i] = (i-1)/S + 1;
	for(int i=1; i<=n; i+=S) L[++res] = i, R[res] = i+S-1; R[res] = n;

	for(int i=1; i<=m; i++){
		q[i] = (Node){read(), read(), read(), read(), i};
	} sort(q+1, q+m+1, cmp);
	
	int l = 1, r = 0;
	for(int i=1; i<=m; i++){
		while(r < q[i].r) Add(a[++r]); while(r > q[i].r) Del(a[r--]);
		while(l < q[i].l) Del(a[l++]); while(l > q[i].l) Add(a[--l]);
		ans[q[i].id] = Qu(q[i].a, q[i].b);
 	} for(int i=1; i<=m; i++) printf("%d\n", ans[i]);
 	return 0;
}

例3: P3246 [HNOI2016]序列

考虑[l, r] 到 [l, r+1]的变化, 多出来的就是 [l, r+1], [l+1, r+1], [l+2, r+1] ... [r+1, r+1] 这些区间的最小值的和

不妨设 p 为l, r+1之间最小值的位置, 那么(p - l + 1) * a[p] 就是 [l, r+1]---[p, r+1] 这些区间的贡献

不妨设 p2 为 p+1, r+1之间的最小值的位置, 那么(p2 - p) * a[p2] 就是 [p+1, r+1] --- [p2, r+1] 的贡献

然后 p3 , p4 知道r+1是最小的

我们考虑预处理这个过程, 首先用单调栈处理前后第一个小于它的位置pre, 与suf

fl[i] 表示从1 -- i 的用上述过程算出来的值 fl[i] = fl[pre_i] + (i - pre_i) * a[i]

然后p+1 到 r+1 的贡献就是 fl[r+1] - fl[p+1] 因为r+1 一定是从 p+1转移了几次过来的, 所以可以相减

然后预处理fr[i]表示从 i--n的, 转移时套一个st表查最小就可以了

#include<bits/stdc++.h>
#define N 200050
using namespace std;
typedef long long ll;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
int n, m, a[N], sta[N];
int pre[N], suf[N];
int st[N][20], lg[N];
ll ans[N], Sum, fl[N], fr[N], pos[N];
struct Node{int l, r, id;}q[N]; 
bool cmp(Node a, Node b){ 
	if(pos[a.l] == pos[b.l]) return a.r < b.r; 
	return pos[a.l] < pos[b.l];
}
int RMQ(int l, int r){
	int x = lg[r - l + 1];
	if(a[st[l][x]] < a[st[r-(1<<x)+1][x]]) return st[l][x];
	else return st[r-(1<<x)+1][x];
}
void L_Add(int L, int R){
	int p = RMQ(L, R); Sum += fl[L] - fl[p] + 1ll * (R - p + 1) * a[p]; 
} 
void R_Add(int L, int R){
	int p = RMQ(L, R); Sum += fr[R] - fr[p] + 1ll * (p - L + 1) * a[p];
}
void L_Del(int L, int R){
	int p = RMQ(L, R); Sum -= fl[L] - fl[p] + 1ll * (R - p + 1) * a[p]; 
}
void R_Del(int L, int R){
	int p = RMQ(L, R); Sum -= fr[R] - fr[p] + 1ll * (p - L + 1) * a[p]; 
}
int main(){
	n = read(), m = read();
	for(int i=1; i<=n; i++) a[i] = read(), st[i][0] = i;
	
	for(int i=2; i<=n; i++) lg[i] = lg[i/2] + 1;
	for(int i=1; (1<<i) <= n; i++)
		for(int j=1; j+(1<<i)-1<=n; j++){
			if(a[st[j][i-1]] < a[st[j+(1<<(i-1))][i-1]]) st[j][i] = st[j][i-1];
			else st[j][i] = st[j+(1<<(i-1))][i-1];
		}
		
	int top = 0;
	for(int i=1; i<=n; i++){
		while(top && a[i] < a[sta[top]]){ 
			suf[sta[top]] = i; top--;
		} pre[i] = sta[top]; sta[++top] = i;
	} while(top) suf[sta[top--]] = n+1;
	
	for(int i=1; i<=n; i++) fr[i] = fr[pre[i]] + 1ll * (i - pre[i]) * a[i];
	for(int i=n; i>=1; i--) fl[i] = fl[suf[i]] + 1ll * (suf[i] - i) * a[i];
	
	int S = sqrt(n);
	for(int i=1; i<=n; i++) pos[i] = (i-1) / S + 1;
	
	for(int i=1; i<=m; i++) q[i] = (Node){read(), read(), i};
	sort(q+1, q+m+1, cmp);
	
	int l = 1, r = 0;
	for(int i=1; i<=m; i++){
		while(r < q[i].r) R_Add(l, ++r); while(r > q[i].r) R_Del(l, r--);
		while(l < q[i].l) L_Del(l++, r); while(l > q[i].l) L_Add(--l, r);
		ans[q[i].id] = Sum;
	} 
	for(int i=1; i<=m; i++) printf("%lld\n", ans[i]);
	return 0;
}

待修莫队

开始不是很理解, 其实就是加一个时间戳t, 然后跟着移动就可以了, 复杂度分析有点毒瘤(from scarlyw)

 来一道例题

 P1903 [国家集训队]数颜色

我们用一个struct 记录每个修改的 pos, pre(原来颜色), nxt(改后颜色)

每个询问记录 l, r, t(正好在它前面的修改的时间戳), id

排序按 pos[l] 为第一关键字, pos[r] 为第二关键字, t为第3关键字

l, r 移动同普通莫队, 最后要把当前t 移动到询问的 t, 如果修改在l, r内就修改, 否则不管

#include<bits/stdc++.h>
#define N 500500
#define M 1000050
using namespace std;
int read(){
    int cnt = 0, f = 1; char ch = 0;
    while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
    while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
    return cnt * f;
}
int n, m, Qn, Cn, a[N], b[N], pos[N], Sum, cnt[M], ans[N];
struct Quary{int l, r, t, id;}q[N];
struct Change{int x, pre, nxt;}c[N];
bool cmp(Quary a, Quary b){
	return (pos[a.l] < pos[b.l]) || (pos[a.l] == pos[b.l] && pos[a.r] < pos[b.r]) 
	|| (pos[a.l] == pos[b.l] && pos[a.r] == pos[b.r] && a.t < b.t);
}
void Add(int x){if(!cnt[x]) Sum++; cnt[x]++;}
void Del(int x){if(cnt[x]==1) Sum--; cnt[x]--;}
int main(){
	n = read(), m = read(); int S = pow(n, 2/3);
	for(int i=1; i<=n; i++)  a[i] = b[i] = read(), pos[i] = (i-1)/S + 1;
	while(m--){
		char op[3]; scanf("%s", op);
		if(op[0] == 'Q') q[++Qn] = (Quary){read(), read(), Cn, Qn};
		if(op[0] == 'R'){ int p = read(), v = read();
			c[++Cn] = (Change){p, b[p], v}; b[p] = v;
		}
	} sort(q+1, q+Qn+1, cmp);
	int l = 1, r = 0, now = 0;
	for(int i=1; i<=Qn; i++){
		while(l < q[i].l) Del(a[l++]);
		while(l > q[i].l) Add(a[--l]);
		while(r < q[i].r) Add(a[++r]);
		while(r > q[i].r) Del(a[r--]);
		while(now < q[i].t){ ++now;
			if(q[i].l <= c[now].x && c[now].x <= q[i].r){
				Del(c[now].pre); Add(c[now].nxt);
			} a[c[now].x] = c[now].nxt;
		}
		while(now > q[i].t){ 
			if(q[i].l <= c[now].x && c[now].x <= q[i].r){
				Del(c[now].nxt); Add(c[now].pre);
			} a[c[now].x] = c[now].pre; now--;
		}
		ans[q[i].id] = Sum; 
	} for(int i=1; i<=Qn; i++) printf("%d\n", ans[i]);
	return 0;
}

树上莫队

我接触到的有两种写法, 一种是基于树分块的, 一种是基于dfs序的, 我学的是基于dfs序的, 树分块了解一下就好

 我们建立出dfs序后(例如 1 2 2 3 4 4 3 1), 记录st[u], ed[u] 表示起始于终止

如果lca(u, v) = u, 那么v在u的子树中, 我们可以令左端点为st[u], 右端点为 st[v](这种情况包涵了lca)

否则, v, u 在不同子树中, 我们令左端点为 ed[u], 右端点为 st[v]

这种方法巧妙就在, 如果一个点不在路径上, 要么压根没有到, 要么到了两次(进去了又出来了)

所以我们记录一个vis, 如果vis为1 就删除, 为0就添加, 最后加上lca就可以了


例1:WOJ1196 苹果树 

裸模板

#include<bits/stdc++.h>
#define N 200050
using namespace std;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
int first[N], nxt[N], to[N], tot;
void add(int x, int y){
	nxt[++tot] = first[x], first[x] = tot, to[tot] = y;
}
int n, m, col[N], rt, Bel[N], vis[N], Sum;
int fa[N][20], dep[N], pos[N], st[N], ed[N], sign, cnt[N], ans[N];
struct Node{ int l, r, a, b, id, lca;} q[N];
bool cmp(Node a, Node b){ 
	if(Bel[a.l] == Bel[b.l]) return a.r < b.r;
	return Bel[a.l] < Bel[b.l];
}
void dfs(int u, int f){
	for(int i=1; i<=18; i++) fa[u][i] = fa[fa[u][i-1]][i-1];
	pos[++sign] = u; st[u] = sign;
	for(int i=first[u];i;i=nxt[i]){
		int t = to[i]; if(t == f) continue;
		fa[t][0] = u; dep[t] = dep[u] + 1; dfs(t, u);
	} pos[++sign] = u; ed[u] = sign; 
}
void calc(int x){
	if(cnt[col[x]]) Sum--;
	if(!vis[x]) cnt[col[x]]++;
	else cnt[col[x]]--;
	if(cnt[col[x]]) Sum++; vis[x] ^= 1;
}
int LCA(int x, int y){
	if(dep[x] < dep[y]) swap(x, y);
	for(int i=18; i>=0; i--)
		if(dep[fa[x][i]] >= dep[y]) x = fa[x][i];
	if(x == y) return x;
	for(int i=18; i>=0; i--)
		if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
	return fa[x][0];
}
int main(){
	n = read(), m = read();
	for(int i=1; i<=n; i++) col[i] = read();
	for(int i=1; i<=n; i++){
		int x = read(), y = read();
		if(!x || !y) rt = x + y;
		else add(x, y), add(y, x);
	} dep[rt] = 1; dfs(rt, 0);
	int S = sqrt(sign); 
	for(int i=1; i<=sign; i++) Bel[i] = (i-1)/S + 1;
	for(int i=1; i<=m; i++){
		int u = read(), v = read(), a = read(), b = read();
		if(st[u] > st[v]) swap(u, v);
		int lca = LCA(u, v); 
		if(u == lca) q[i] = (Node){ st[u]+1, st[v], a, b, i, lca};
		else q[i] = (Node){ ed[u], st[v], a, b, i, lca};
	} sort(q+1, q+m+1, cmp);
	int l = 1, r = 0;
	for(int i=1; i<=m; i++){
		while(r < q[i].r) calc(pos[++r]); while(r > q[i].r) calc(pos[r--]);
		while(l < q[i].l) calc(pos[l++]); while(l > q[i].l) calc(pos[--l]);
		calc(q[i].lca);
		if(cnt[q[i].a] && cnt[q[i].b] && q[i].a != q[i].b) ans[q[i].id] = Sum-1; 
		else ans[q[i].id] = Sum; 
		calc(q[i].lca);
	} for(int i=1; i<=m; i++) printf("%d\n", ans[i]);
	return 0; 
}

例2:P4074 [WC2013]糖果公园

也是裸模板, 细节有点多

#include<bits/stdc++.h>
#define N 200050
using namespace std;
typedef long long ll;
#define int long long
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
int n, m, Q;
int v[N], w[N], b[N], col[N], vis[N];
int first[N], nxt[N], to[N], tot;
void add(int x, int y){
	nxt[++tot] = first[x], first[x] = tot, to[tot] = y;
}

int fa[N][20], dep[N], pos[N], st[N], ed[N], sign;
void dfs(int u, int f){
	for(int i=1; i<=18; i++) fa[u][i] = fa[fa[u][i-1]][i-1];
	st[u] = ++sign; pos[sign] = u;
	for(int i=first[u];i;i=nxt[i]){
		int t = to[i]; if(t == f) continue;
		fa[t][0] = u; dep[t] = dep[u] + 1; dfs(t, u);
	} ed[u] = ++sign; pos[sign] = u;
}
int LCA(int x, int y){
	if(dep[x] < dep[y]) swap(x, y);
	for(int i=18; i>=0; i--)
		if(dep[fa[x][i]] >= dep[y]) x = fa[x][i];
	if(x == y) return x;
	for(int i=18; i>=0; i--)
		if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
	return fa[x][0];
}

int Bel[N], Cn, Qn, cnt[N];
struct Node{ int l, r, t, lca, id;} q[N];
struct Modify{ int x, pre, nxt;} c[N];
bool cmp(Node a, Node b){ 
	return (Bel[a.l] < Bel[b.l]) || (Bel[a.l] == Bel[b.l] && Bel[a.r] < Bel[b.r]) 
	|| (Bel[a.l] == Bel[b.l] && Bel[a.r] == Bel[b.r] && a.t < b.t);
}

ll ans[N], Sum;
void calc(int x){
	if(cnt[col[x]]) Sum -= v[col[x]] * w[cnt[col[x]]];
	if(vis[x]) cnt[col[x]]--; else cnt[col[x]]++;
	if(cnt[col[x]]) Sum += v[col[x]] * w[cnt[col[x]]]; 
	vis[x] ^= 1; 
} 

signed main(){
	n = read(), m = read(), Q = read();
	for(int i=1; i<=m; i++) v[i] = read();
	for(int i=1; i<=n; i++) w[i] = read(), w[i] += w[i-1];
	for(int i=1; i<n; i++){
		int x = read(), y = read();
		add(x, y); add(y, x);
	} dep[1] = 1; dfs(1, 0);
	for(int i=1; i<=n; i++) col[i] = b[i] = read();
	int S = pow(sign, 2.0/3.0); 
	for(int i=1; i<=sign; i++) Bel[i] = (i-1)/S + 1;
	
	for(int i=1; i<=Q; i++){
		int op = read(), x = read(), y = read();
		if(op == 0) c[++Cn] = (Modify){x, b[x], y}, b[x] = y;
		if(op == 1){
			int lca = LCA(x, y);
			if(st[x] > st[y]) swap(x, y);
			if(x == lca) q[++Qn] = (Node){st[x], st[y], Cn, 0, Qn};
			else q[++Qn] = (Node){ed[x], st[y], Cn, lca, Qn};
		}
	} sort(q+1, q+Qn+1, cmp);
	
	int l = 1, r = 0, now = 0;
	for(int i=1; i<=Qn; i++){
		while(l < q[i].l) calc(pos[l++]); while(l > q[i].l) calc(pos[--l]);
		while(r < q[i].r) calc(pos[++r]); while(r > q[i].r) calc(pos[r--]);
		if(q[i].lca) calc(q[i].lca); 
		while(now < q[i].t){ 
			now++;
			if(vis[c[now].x]){ 
				calc(c[now].x), col[c[now].x] = c[now].nxt, calc(c[now].x); 
			} col[c[now].x] = c[now].nxt;
		}
		while(now > q[i].t){
			if(vis[c[now].x]){
				calc(c[now].x), col[c[now].x] = c[now].pre, calc(c[now].x);
			} col[c[now].x] = c[now].pre; now--;
		}
		ans[q[i].id] = Sum; 
		if(q[i].lca) calc(q[i].lca); 
	}
	for(int i=1; i<=Qn; i++) printf("%lld\n", ans[i]);
 	return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值