AT1219 歴史の研究

原题链接
回滚莫队模板.
排序关键字:(1)左右端点所在块编号是否相同(2)左端点所在块编号(3)右端点
若左右端点所在块编号相同,则暴力计算答案。
对于左端点所在块编号相同的询问,块内暴力处理,块外累加答案。
时间复杂度O(n√n)
代码如下:

#include <cstdio>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <cstring>

using namespace std;

#define ll long long

inline int read(){
    int res = 0, pdf = 0; char ch = getchar();
    while(!isdigit(ch)) pdf = ch == '-', ch = getchar();
    while(isdigit(ch)) res = (res<<3) + (res<<1) + (ch^48), ch = getchar();
    return pdf ? -res : res;
} 

inline void Print(ll x) {
	if (x < 0) x = -x, putchar('-'); 
	if (x < 10) putchar(x + '0'); 
	else {
		Print(x / 10); 
		putchar(x % 10 + '0'); 
	}
} 

const int N = 1e5 + 100; 
int n, q, block, c[N], br[N], pos[N];
int tmp[N], len; 

struct Ask {
	int l, r, id; ll ans; 
};
Ask a[N]; 
bool cmp1(Ask x, Ask y) {
	if ((pos[x.l] == pos[x.r]) != (pos[y.l] == pos[y.r])) return (pos[x.l] == pos[x.r]) > (pos[y.l] == pos[y.r]); 
	if (pos[x.l] != pos[y.l]) return pos[x.l] < pos[y.l]; 
	return x.r < y.r; 
}
bool cmp2(Ask x, Ask y) {
	return x.id < y.id; 
}

int ton[N]; ll res; 

void Solve() {
	for (int i = 1; i <= q; ++i) {
		if (pos[a[i].l] == pos[a[i].r]) {
			memset(ton, 0, sizeof(ton)); 
			for (int j = a[i].l; j <= a[i].r; ++j) {
				++ton[c[j]]; 
				a[i].ans = max(a[i].ans, (ll)ton[c[j]] * (ll)tmp[c[j]]); 
			}
		} else if (pos[a[i].l] != pos[a[i - 1].l]) {
			memset(ton, 0, sizeof(ton)); res = 0; 
			for (int j = br[pos[a[i].l]] + 1; j <= a[i].r; ++j) {
				++ton[c[j]]; 
				res = max(res, (ll)ton[c[j]] * (ll)tmp[c[j]]); 
			}
			a[i].ans = res; 
			for (int j = a[i].l; j <= br[pos[a[i].l]]; ++j) {
				++ton[c[j]]; 
				a[i].ans = max(a[i].ans, (ll)ton[c[j]] * (ll)tmp[c[j]]); 
			}
			for (int j = a[i].l; j <= br[pos[a[i].l]]; ++j) {
				--ton[c[j]]; 
			}
		} else {
			for (int j = a[i - 1].r + 1; j <= a[i].r; ++j) {
				++ton[c[j]]; 
				res = max(res, (ll)ton[c[j]] * (ll)tmp[c[j]]); 
			}
			a[i].ans = res; 
			for (int j = a[i].l; j <= br[pos[a[i].l]]; ++j) {
				++ton[c[j]]; 
				a[i].ans = max(a[i].ans, (ll)ton[c[j]] * (ll)tmp[c[j]]); 
			}
			for (int j = a[i].l; j <= br[pos[a[i].l]]; ++j) {
				--ton[c[j]]; 
			}
		}
	}
}

int main() {
	n = read(); q = read(); 
	for (int i = 1; i <= n; ++i) tmp[i] = c[i] = read(); 
	sort(tmp + 1, tmp + n + 1); 
	int len = unique(tmp + 1, tmp + n + 1) - (tmp + 1); 
	for (int i = 1; i <= n; ++i) c[i] = lower_bound(tmp + 1, tmp + len + 1, c[i]) - tmp;  
	block = sqrt(n);
	for (int i = 1; i <= n; ++i) {
		pos[i] = i / block + 1; 
		if (pos[i] != pos[i - 1]) br[pos[i - 1]] = i - 1;  
	}
	br[pos[n]] = n; 
	for (int i = 1; i <= q; ++i) {
		a[i].l = read(); a[i].r = read(); a[i].id = i; 
	}
	sort(a + 1, a + q + 1, cmp1); 
	Solve(); 
	sort(a + 1, a + q + 1, cmp2); 
	for (int i = 1; i <= q; ++i) {
		Print(a[i].ans); putchar('\n'); 
	}
	return 0; 
}

补充:

  1. 若增加操作可以区间转移,删除不能区间转移,则为只使用增加操作的回滚莫队,删除操作交给回滚解决。类似地,有只使用删除操作的回滚莫队.
  2. 之前题解写的是啥呀 重新整理算法流程:
    (1)对询问进行排序:左端点所在块的编号升序为第一关键字,右端点升序为第二关键字.
    (2)顺序处理询问:①若左右端点所在块的编号一致,则暴力统计答案.
    ②初始 l a = 0 la=0 la=0, 若左端点所在块的编号与 l a la la 不一致,则清空桶并使当前 l = 左端点所在块的右端点 + 1 l = 左端点所在块的右端点+1 l=左端点所在块的右端点+1, 并用左端点所在块的编号更新 l a la la.
    ③转移询问区间.
    回滚使 l = 左端点所在块的右端点 + 1 l = 左端点所在块的右端点+1 l=左端点所在块的右端点+1.
    代码如下:
#include <bits/stdc++.h>

using namespace std;

#define ll long long

inline int read() {
	int x = 0, f = 0; char ch = getchar();
	while (!isdigit(ch)) f = ch == '-', ch = getchar();
	while (isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
	return f ? -x : x;
}

const int N = 100010; 

int n, m, col[N], tmp[N], ton[N], ton_[N], len; 
int block, pos[N], br[N]; 
ll res[N], now;  
struct Query {
	int l, r, id; 
	bool operator < (const Query &that) const {
		if (pos[l] != pos[that.l]) return pos[l] < pos[that.l]; 
		return r < that.r;  
	}
};
Query q[N]; 

void add(int c) {
	++ton[c]; 
	now = max(now, 1ll * ton[c] * tmp[c]); 
}

void del(int c) {
	--ton[c]; 
}

void solve() {
	int l = 1, r = 0, la = 0; 
	for (int i = 1; i <= m; ++i) {
		int left = q[i].l, right = q[i].r, id = q[i].id; 
		if (pos[left] == pos[right]) {
			for (int j = left; j <= right; ++j) {
				++ton_[col[j]];  
				res[id] = max(res[id], 1ll * tmp[col[j]] * ton_[col[j]]); 
			}
			for (int j = left; j <= right; ++j) {
				--ton_[col[j]];  
			}
			continue; 
		}
		if (pos[left] != la) {
			int l_ = br[pos[left]] + 1, r_ = br[pos[left]]; 
			while (l < l_) del(col[l++]); 
			while (r > r_) del(col[r--]); 
//			while (r < r_) del(col[r++]); 
			now = 0; 
			la = pos[left]; 
		}
		while (r < right) add(col[++r]); 
		ll tp = now; 
		while (l > left) add(col[--l]); 
		res[id] = now;
		while (l < br[pos[left]] + 1) del(col[l++]);
		now = tp;  
	}
}

int main() {
	n = read(); m = read(); 
	for (int i = 1; i <= n; ++i) col[i] = tmp[i] = read();
	sort(tmp + 1, tmp + n + 1); 
	len = unique(tmp + 1, tmp + n + 1) - (tmp + 1); 
	for (int i = 1; i <= n; ++i) {
		col[i] = lower_bound(tmp + 1, tmp + len + 1, col[i]) - tmp; 
	}
	block = sqrt(n); 
	for (int i = 1; i <= n; ++i) pos[i] = i / block + 1; 
	for (int i = 1; i <= n; ++i) if (pos[i] != pos[i + 1]) br[pos[i]] = i;
	for (int i = 1; i <= m; ++i) {
		q[i].l = read(); q[i].r = read(); q[i].id = i; 
	}
	sort(q + 1, q + m + 1); 
	solve(); 
	for (int i = 1; i <= m; ++i) {
		printf("%lld\n", res[i]); 
	}
}
  1. 若序列长度为 n n n, 共有 m m m 个询问, 则时间复杂度为 O ( m b + n 2 b ) O(mb+\frac {n^2} {b}) O(mb+bn2), 其中 b b b 为块长,当 b = n m b=\frac{n}{\sqrt{m}} b=m n 时时间复杂度最优,为 O ( n m ) O(n\sqrt{m}) O(nm ).
  2. 题单:P5906 【模板】回滚莫队&不删除莫队 小 trick: 利用 c l e a r clear clear 数组清空桶.
    P3709 大爷的字符串题 询问区间众数.
    总结:求区间众数的方法
    (1) 离线:回滚莫队 O ( n m ) O(n\sqrt{m}) O(nm ).
    (2) 在线:蒲公英(分块).
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值