算法学习笔记(3):莫队算法

莫队算法

莫队算法是由莫涛发明的离线算法, 有很多应用 , 实现起来也较为简单, 可以称作优雅的暴力, 大部分情况是将所有询问离线下来,通过合理的排序, 然后进行 O(1) 的转移, 由于是通过分块排序和 O(1) 转移, 所以看起来既暴力又透出一丝优雅。

普通莫队

直接上题吧

P1494 [国家集训队] 小 Z 的袜子

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 5e4 + 10;
int n, m, raw[N], block, cnt[N]; 
struct opt{
	int l, r, id;
	bool operator < (const opt &x) const {
		if (l / block != x.l / block) return l < x.l;
		return ((l / block) & 1) ? r < x.r : r > x.r;
	}
}a[N];
#define lint long long
lint sum;
void add(int k) {
	sum += cnt[k];
	cnt[k]++;
}
void del(int k) {
	cnt[k]--;
	sum -= cnt[k];
}
lint gcd(lint a, lint b) {
	return b ? gcd(b, a % b) : a;
}
lint ans1[N], ans2[N];
int main() {
	scanf("%d%d", &n, &m);
	block = sqrt(n);
	for (int i = 1; i <= n; i++) scanf("%d", &raw[i]);
	for (int i = 1; i <= m; i++) { scanf("%d%d", &a[i].l, &a[i].r); a[i].id = i; }
	sort(a + 1, a + 1 + m);
//	for (int i = 1; i <= m; i++) {
//		printf("%d %d %d\n", a[i].l, a[i].r, a[i].id);
//		
//	}
//	puts("fuck");
	for (int i = 1, l = 1, r = 0; i <= m; i++) {
//		printf("%d\n", i);
//		printf("%d %d\n", l, r);
		int pos = a[i].id;
		if (a[i].l == a[i].r) {
			ans1[pos] = 0, ans2[pos] = 1;
			continue; 
		}
		while (l > a[i].l) add(raw[--l]); 
		while (r < a[i].r) add(raw[++r]);
		while (l < a[i].l) del(raw[l++]);
		while (r > a[i].r) del(raw[r--]);
		ans1[pos] = sum;
		ans2[pos] = 1ll * (r - l + 1) * (r - l) / 2;
	}
	for (int i = 1; i <= m; i++) {
		if (ans1[i] == 0 && ans2[i] == 1) {
			printf("%lld/%lld\n", ans1[i], ans2[i]);
			continue;
		}
		else {
			lint _gcd = gcd(ans1[i], ans2[i]);
			printf("%lld/%lld\n", ans1[i] / _gcd, ans2[i] / _gcd);
		}
 	}
	return 0;
}

P3709 大爷的字符串题

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N = 2e5 + 10; 
int n, m, raw[N], val[N], block;

struct opt{
	int l, r, id; 
	bool operator < (const opt &x) const{
		if (l / block != x.l / block) return l < x.l;
		return ((l / block) & 1) ? r < x.r : r > x.r;
	}
}a[N];
void disc() {
	for (int i = 1; i <= n; i++) val[i] = raw[i];
	sort(raw + 1, raw + 1 + n);
	int cnt = unique(raw + 1, raw + 1 + n) - raw - 1;
	for (int i = 1; i <= n; i++) {
		int pos = lower_bound(raw + 1, raw + 1 + cnt, val[i]) - raw;
		val[i] = pos;
	} 
}
int maxn = 0, num[N], cnt[N], ans[N];
inline void add(int x) {
	if (cnt[val[x]] + 1 > maxn) maxn++;
	num[cnt[val[x]]]--, cnt[val[x]]++, num[cnt[val[x]]]++;
}
inline void del(int x) {
	if (num[maxn] == 1 && cnt[val[x]] == maxn) maxn--;
	num[cnt[val[x]]]--, cnt[val[x]]--, num[cnt[val[x]]]++;
}
int main() {
	scanf("%d%d", &n, &m);
	block = sqrt(n);
	for (int i = 1; i <= n; i++) scanf("%d", &raw[i]);	
	disc();
	for (int i = 1; i <= m; i++) {
		scanf("%d%d", &a[i].l, &a[i].r);
		a[i].id = i;
	}
	sort(a + 1, a + 1 + m);
	for (int i = 1, l = 1, r = 0; i <= m; i++) {
		while (l < a[i].l) del(l++);
		while (l > a[i].l) add(--l);
		while (r > a[i].r) del(r--);
		while (r < a[i].r) add(++r);
		ans[a[i].id] = maxn;
	}
	for (int i = 1; i <= m; i++) printf("%d\n", -ans[i]);
	return 0;
}

带修莫队

简而言之, 是加上时间的一维, 并且把 r 也分块排序, 这样才能保证时间复杂度, 证明很简单, 不再多说。 区间的转移在普通莫队做法的基础上, 加一些修改, 修改实际上就是删一些点, 加一些点。

上题吧

P1903 [国家集训队] 数颜色 / 维护队列

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;

namespace Input {
	int read() {
		int x = 0, f = 1; char c = getchar();
		while (c < '0' || c > '9') {
			if (c == '-') f = -1;
			c = getchar();
		}
		while (c >= '0' && c <= '9') {
			x = (x << 1) + (x << 3) + (c ^ 48);
			c = getchar();
		}
		return x * f;
	}
	void write(int x) {
		if (x < 10) {
			putchar(x + 48);
			return;
		} 
		write(x / 10);
		putchar(x % 10 + 48);
	}
}using namespace Input;

const int N = 133333 + 10;
//const int NN = 150;
int n, m, col[N], block;
struct Query{
	int x, y, tim, id;
	bool operator < (const Query &a) const{
		if (x / block == a.x / block) {
			if (y / block == a.y / block) return (y / block) & 1 ? tim < a.tim : tim > a.tim;
			return y / block < a.y / block;	 	
		}
		return x / block < a.x / block;
	}
//	Query(int _x, int _y, int _tim, int _id) { x = _x, y = _y, tim = _tim, id = _id; }
}Q[N];
struct Change{
	int x, y;
}R[N];
int qcnt, rcnt, cnt[1000010], sum, ans[N];
// op 1 = Q 0 = R;
void add(int x) {
	if (!cnt[x]) sum++;
	cnt[x]++;
}
void del(int x) {
	cnt[x]--;
	if (!cnt[x]) sum--;
}
int main() {
//	write(18530853);
//	scanf("%d%d", &n, &m);
	n = read(), m = read(); 
	block = pow(n, 2.0 / 3.0);
	for (int i = 1; i <= n; i++) col[i] = read(); 
//	scanf("%d", col[i]);
	char st[5];
	for (int i = 1; i <= m; i++) {
		int x, y; scanf("%s", st);
		x = read(), y = read();
		if (*st == 'Q') Q[++qcnt] = (Query){x, y, rcnt, qcnt};
		else R[++rcnt] = (Change){x, y};
	}
//	for (int i = 1; i <= qcnt; i++) {
//		printf("%d\n", Q[i].id);
//	}
	sort(Q + 1, Q + qcnt + 1);
	int L = 1, RR = 0, last = 0;
	for (int i = 1; i <= qcnt; i++) {
		while (L < Q[i].x) del(col[L++]);
		while (L > Q[i].x) add(col[--L]);
		while (RR < Q[i].y) add(col[++RR]);
		while (RR > Q[i].y) del(col[RR--]);
		while (last < Q[i].tim) {
			last++;
			if (L <= R[last].x && R[last].x <= RR) {
				add(R[last].y);
				del(col[R[last].x]);
			}
			swap(col[R[last].x], R[last].y);
		}  
		while (last > Q[i].tim) {
			if (L <= R[last].x && R[last].x <= RR) {
				add(R[last].y);
				del(col[R[last].x]);
			}
			swap(col[R[last].x], R[last].y);
			last--;
		}
		ans[Q[i].id] = sum;
	}
	for (int i = 1; i <= qcnt; i++) { write(ans[i]); printf("\n"); }
//	printf("%d\n", ans[i]);
	return 0;
}

Machine Learning

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
namespace Input {
	int read() {
		int x = 0, f = 1; char c = getchar();
		while (c < '0' || c > '9') {
			if (c == '-') f = -1;
			c = getchar();
		}
		while (c >= '0' && c <= '9') {
			x = (x << 1) + (x << 3) + (c ^ 48);
			c = getchar();
		}
		return x * f;
	}
	void write(int x) {
		if (x < 10) {
			putchar(x + 48);
			return;
		}
		write(x / 10);
		putchar(x % 10 + 48);
	}
}using namespace Input;
const int N = 1e5 + 10;
int n, q, raw[N << 1], col[N], qcnt, ccnt, block;
struct Query{
	int l, r, tim, id;
	Query() :l(), r(), tim(), id(){}
	Query(int _l, int _r, int _tim, int _id) :l(_l), r(_r), tim(_tim), id(_id){}
	bool operator < (const Query &x) const{
		if (l / block == x.l / block) {
			if (r / block == x.r / block) return (r / block) & 1 ? tim < x.tim : tim > x.tim;
			return r / block < x.r / block;
		}
		return l / block < x.l / block;
	}
}Q[N];
struct Change{
	int p, x;
}C[N];
void disc() {
	for (int i = 1; i <= n; i++) col[i] = raw[i];
	for (int i = n + 1; i <= n + ccnt; i++) raw[i] = C[i - n].x;
	sort(raw + 1, raw + 1 + n + ccnt);
	int cnt = unique(raw + 1, raw + 1 + n + ccnt) - raw - 1;
	for (int i = 1; i <= n; i++) {
		int pos = lower_bound(raw + 1, raw + 1 + cnt, col[i]) - raw;
		col[i] = pos;
	}
	for (int i = 1; i <= ccnt; i++) {
		int pos = lower_bound(raw + 1, raw + 1 + cnt, C[i].x) - raw;
		C[i].x = pos;
	} 
}
int cnt[N << 1], num[N << 1], ans[N];
void del(int x) {
	num[cnt[x]]--;
	cnt[x]--;
	num[cnt[x]]++;
}
void add(int x) {
	num[cnt[x]]--;
	cnt[x]++;
	num[cnt[x]]++;
}
int main() {
	n = read(), q = read();
	block = pow(n, 2.0 / 3.0);
	for (int i = 1; i <= n; i++) raw[i] = read();
	for (int i = 1; i <= q; i++) {
		int t, x, y; t = read(), x = read(), y = read();
		if (t == 1) Q[++qcnt] = Query(x, y, ccnt, qcnt);
		else C[++ccnt] = (Change){x, y};
	}  
	disc();
	sort(Q + 1, Q + 1 + qcnt);
	int L = 1, R = 0, last = 0; 
	for (int i = 1; i <= qcnt; i++) {
		while (L > Q[i].l) add(col[--L]);
		while (L < Q[i].l) del(col[L++]);
		while (R > Q[i].r) del(col[R--]);
		while (R < Q[i].r) add(col[++R]);
		while (Q[i].tim > last) {
			last++;
			if (L <= C[last].p && C[last].p <= R) {
				del(col[C[last].p]);
				add(C[last].x);
			}
			swap(col[C[last].p], C[last].x);
		}
		while (Q[i].tim < last) {
			if (L <= C[last].p && C[last].p <= R) {
				del(col[C[last].p]);
				add(C[last].x);
			}
			swap(col[C[last].p], C[last].x);
			last--;
		}
		for (int j = 1; j <= R - L + 2; j++) 
			if (num[j] == 0) { ans[Q[i].id] = j; break; }
	}
	for (int i = 1; i <= qcnt; i++) { write(ans[i]); printf("\n"); }
	return 0;
}

回滚莫队

回滚莫队应用于只能删点或者只能加点的情况下, 那么怎么办? 就先拿只加不减举例, 对于所有左端点处于同一块 (l,r) 的询问, 若询问为 (L,R) 已知 R 是递增的,所以对于右端点就直接加, 但左端点不一定递增, 所以我们取起点为 r , 每次将 (L,r) 的点加上, 对于下一次询问, 回滚到 r, 既之前统计的 (L,r) 的答案直接不要了, 在重新计算, 反正对于左端点在同一块的询问, 每次计算的总代价不超过 O(n)

题目

P5906 【模板】回滚莫队&不删除莫队

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
namespace Input{
	int read() {
		int x = 0, f = 1; char c = getchar();
		while (c < '0' || c > '9') {
			if (c == '-') f = -1;
			c = getchar();
		}
		while (c >= '0' && c <= '9') {
			x = (x << 1) + (x << 3) + (c ^ 48);
			c = getchar();
		}
		return x * f;
	}
	void write(int x) {
		if (x < 10) {
			putchar(x + 48);
			return;
		}
		write(x / 10);
		putchar(x % 10 + 48); 
	}
}using namespace Input;
const int N = 2e5 + 10;
const int NN = 1500;
int n, m, raw[N], val[N];
void disc() {
	sort(raw + 1, raw + 1 + n);
	int cnt = unique(raw + 1, raw + 1 + n) - raw - 1;
	for (int i = 1; i <= n; i++) 
		val[i] = lower_bound(raw + 1, raw + 1 + cnt, val[i]) - raw;
}
namespace Block{
	int LL[NN], RR[NN], belong[N], tot, block;
	void build() {
		block = max(1, (int)(n * 1.0 / (sqrt(m) * 1.0)));
		tot = n / block;
		for (int i = 1; i <= tot; i++) {
			LL[i] = (i - 1) * block + 1;
			RR[i] = i * block;
		}
		if (RR[tot] < n) tot++, LL[tot] = RR[tot - 1] + 1, RR[tot] = n;
		for (int i = 1; i <= tot; i++) 
			for (int j = LL[i]; j <= RR[i]; j++) 	
				belong[j] = i;
	}
}using namespace Block;
struct Query{
	int l, r, id;
	bool operator < (const Query &x) const{
		if (belong[l] == belong[x.l]) return r < x.r;
		return belong[l] < belong[x.l]; 
	}
}Q[N];
int lt[N], rt[N], _lt[N], _rt[N], ans[N], clear[N], ccnt, cl1[N], cnt1, cl2[N], cnt2, a[N], b[N]; 
int main() {
//	freopen("P5906_2.in", "r", stdin);
//	freopen("1.out", "w", stdout);
	n = read();
	for (int i = 1; i <= n; i++) val[i] = raw[i] = read();
	disc(); 
	m = read();
	build();
	for (int i = 1; i <= m; i++) 
		Q[i].l = read(), Q[i].r = read(), Q[i].id = i;
	sort(Q + 1, Q + 1 + m);
	int L = 1, R = 0, lst_block = 0, _L, Ans = 0;
	for (int i = 1; i <= m; i++) {
		int l = Q[i].l, r = Q[i].r;
		if (belong[l] == belong[r]) {
			for (int j = l; j <= r; j++) {
				if (!_lt[val[j]]) _lt[val[j]] = j, clear[++ccnt] = val[j];
				else ans[Q[i].id] = max(ans[Q[i].id], j - _lt[val[j]]);
			}
			for (int j = 1; j <= ccnt; j++) _lt[clear[j]] = 0; 
			ccnt = 0;
			continue;
		}
		if (belong[l] != lst_block) {
			for (int j = 1; j <= cnt1; j++) lt[cl1[j]] = 0;
			for (int j = 1; j <= cnt2; j++) rt[cl2[j]] = 0;
			_L = L = RR[belong[l]] + 1, R = RR[belong[l]], Ans = cnt1 = cnt2 = 0;
			lst_block = belong[l];
		}
		while (R < r) {
			R++;
			if (!lt[val[R]]) lt[val[R]] = R, cl1[++cnt1] = val[R];
			rt[val[R]] = R, cl2[++cnt2] = val[R];
			Ans = max(Ans, rt[val[R]] - lt[val[R]]);
		}
		int tmp = Ans;
		while (L > l) {	
			L--;
			if (!_rt[val[L]]) _rt[val[L]] = L, clear[++ccnt] = val[L];
			tmp = max(tmp, max(rt[val[L]], _rt[val[L]]) - L);
		} 
		ans[Q[i].id] = tmp;
		L = _L;
		for (int j = 1; j <= ccnt; j++) _rt[clear[j]] = 0; 
		ccnt = 0;
	}
	for (int i = 1; i <= m; i++) { write(ans[i]); printf("\n"); }
	return 0;
}
//349 61 445

歴史の研究

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
namespace Input{
	int read() {
		int x = 0, f = 1; char c = getchar();
		while (c < '0' || c > '9') {
			if (c == '-') f = -1;
			c = getchar();
		}
		while (c >= '0' && c <= '9') {
			x = (x << 1) + (x << 3) + (c ^ 48);
			c = getchar();
		}
		return x * f;
	}
	void write(long long x) {
		if (x < 10) {
			putchar(x + 48);
			return;
		}
		write(x / 10);
		putchar(x % 10 + 48);
	}
}using namespace Input;
const int N = 2e5 + 10;
const int NN = 1000;
int n, q, raw[N], val[N], block;
void disc() {
	for (int i = 1; i <= n; i++) val[i] = raw[i];
	sort(raw + 1, raw + 1 + n);
	int cnt = unique(raw + 1, raw + 1 + n) - raw - 1;
	for (int i = 1; i <= n; i++) {
		int pos = lower_bound(raw + 1, raw + 1 + cnt, val[i]) - raw;
		val[i] = pos;
	} 
}

namespace Block{
	int LL[NN], RR[NN], belong[N], tot; 
	void build() {
		tot = n / block;
		for (int i = 1; i <= tot; i++) {
			LL[i] = (i - 1) * block + 1;
			RR[i] = i * block;
		}
		if (RR[tot] < n) tot++, LL[tot] = RR[tot - 1] + 1, RR[tot] = n; 
		for (int i = 1; i <= tot; i++) 
			for (int j = LL[i]; j <= RR[i]; j++) 
				belong[j] = i;
	}
}using namespace Block;
struct Query{
	int l, r, id;
	bool operator < (const Query &x) const{
		if (belong[l] == belong[x.l]) return r < x.r;
		return belong[l] < belong[x.l];
	}
}Q[N];
int cnt[N], _cnt[N];
long long ans[N], Ans;
void add(int x, long long &y) {
	cnt[x]++;
	y = max(y, 1ll * cnt[x] * raw[x]);
}
void del(int x) { cnt[x]--; }
int main() {
	n = read(), q = read();
//	printf("%d", n);
	block = n * 1.0 / (sqrt(q) * 1.0);
	for (int i = 1; i <= n; i++) raw[i] = read();
	disc(); build(); 
	for (int i = 1; i <= q; i++) {
		int l, r; l = read(), r = read();
		Q[i] = (Query){l, r, i};	
	}
	sort(Q + 1, Q + 1 + q);
	int L = 1, R = 0, _L = 1, lst_block = 0;
	for (int i = 1; i <= q; i++) {
		int l = Q[i].l, r = Q[i].r;
		if (belong[l] == belong[r]) {
			for (int j = l; j <= r; j++) _cnt[val[j]]++;
			for (int j = l; j <= r; j++) 
				ans[Q[i].id] = max(ans[Q[i].id], 1ll * _cnt[val[j]] * raw[val[j]]);
			for (int j = l; j <= r; j++) _cnt[val[j]]--;	
			continue;
		}
		if (belong[l] != lst_block) {
			while (R > RR[belong[l]]) del(val[R--]);
			while (L < RR[belong[l]] + 1) del(val[L++]);
			Ans = 0, _L = RR[belong[l]] + 1;
			lst_block = belong[l];
		}
		while (R < r) add(val[++R], Ans);
		long long tmp = Ans;
		while (L > l) add(val[--L], tmp);
		ans[Q[i].id] = tmp;
		while (L < _L) del(val[L++]); 
	}
	for (int i = 1; i <= q; i++) { write(ans[i]); printf("\n"); }
	return 0;
}

P4137 Rmq Problem / mex

posted @   qqrj  阅读(46)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示