[回滚莫队] 历史研究

题目描述

IOI 国历史研究的第一人——JOI 教授,最近获得了一份被认为是古代 IOI 国的住民写下的日记。JOI 教授为了通过这份日记来研究古代 IOI 国的生活,开始着手调查日记中记载的事件。

日记中记录了连续 N N N 天发生的事件,大约每天发生一件。

事件有种类之分。第 i i i 天发生的事件的种类用一个整数 X i X_i Xi 表示, X i X_i Xi 越大,事件的规模就越大。

JOI 教授决定用如下的方法分析这些日记:

  • 选择日记中连续的几天 [ L , R ] [L, R] [L,R] 作为分析的时间段;
  • 定义事件 A A A 的重要度 W A W_A WA A × T A A \times T_A A×TA,其中 T A T_A TA 为该事件在区间 [ L , R ] [L, R] [L,R] 中出现的次数。

现在,您需要帮助教授求出所有事件中重要度最大的事件是哪个,并输出其重要度

注意:教授有多组询问。

输入格式

第一行两个空格分隔的整数 N N N Q Q Q,表示日记一共记录了 N N N 天,询问有 Q Q Q 次。

接下来一行 N N N 个空格分隔的整数表示每天的事件种类。

接下来 Q Q Q 行,每行给出 L , R L, R L,R 表示一组询问。

输出格式

输出共有 Q Q Q 行,每行一个整数,表示对应的询问的答案。

样例

样例输入1:

5 5
9 8 7 8 9
1 2
3 4
4 4
1 4
2 4

样例输出1:

9
8
8
16
16

样例输入2:

8 4
9 9 19 9 9 15 9 19
1 4
4 6
3 5
5 8

样例输出2:

27
18
19
19

样例输入3:

12 15
15 9 3 15 9 3 3 8 16 9 3 17
2 7
2 5
2 2
1 12
4 12
3 6
11 12
1 7
2 6
3 5
3 10
7 10
1 4
4 8
4 8

样例输出3:

18
18
9
30
18
15
17
30
18
15
18
16
30
15
15

数据范围

对于 100 % 100\% 100% 的数据, 1 ≤ N , Q ≤ 1 0 5 1 \le N, Q \le 10^5 1N,Q105 1 ≤ X ≤ 1 0 9 1 \le X \le 10^9 1X109 1 ≤ L , R ≤ 1 0 5 1 \le L, R \le 10^5 1L,R105

题解

一道回滚莫队模板题。

由于求最大值是一种只增信息(即可以增加一个数求最大值但不能删除),所以显然不能用普通的莫队。

因此,考虑使用回滚莫队,将询问的左端点排序分块,块内按右端点从小到大排序。不能使用奇偶优化,因为这样会破坏右指针的单调性。

当左端点换块时:清空所有数据,直接将左右指针移到下一个块的右端点处。

对于块内询问:

  • 若左右端点同块:直接从 l l l r r r 枚举,可以保证复杂度。
  • 若左右端点异块:首先保证 r r r 单调递增, l l l 指针进行回滚。每次询问时,先将右指针向右移动,记录下此时的 a n s ans ans t t t,左指针再移动到应该的位置,得到询问的答案,然后将 a n s ans ans 还原为 t t t
#include<bits/stdc++.h>
using namespace std;
int n, m, k;
int block;
int a[100010], d[100010], bl[100010], t[100010];
struct node{
	int l, r, time;
}b[100010];
bool operator<(node p, node q){
	return (bl[p.l] != bl[q.l]) ? (p.l < q.l) : (p.r < q.r);
}
int s[100010];
long long pp[100010];
long long ans = 0;
void add(int x){
	s[x] ++;
	ans = max(ans, (long long)s[x] * d[x]);
}
long long js(int l, int r){
	long long ans = 0;
	memset(t, 0, sizeof(t));
	for(long long i = l; i <= r; ++ i){
		t[a[i]] ++;
		ans = max(ans, (long long)t[a[i]] * d[a[i]]);
	}
	return ans;
}
int main(){
	read(n), read(m);
	for(int i = 1; i <= n; ++ i){
		read(a[i]);
		d[i] = a[i];
	}
	for(int i = 1; i <= m; ++ i){
		read(b[i].l), read(b[i].r);
		b[i].time = i;
	}
	block = pow(n, 0.5);
	for(int i = 1; i <= n; ++ i){
		bl[i] = (i - 1) / block + 1;
	}
	sort(d + 1, d + n + 1); 
	for(int i = 1; i <= n; ++ i){
		a[i] = lower_bound(d + 1, d + n + 1, a[i]) - d;
	}
	int pl = 1, pr = 0, pt = 0;
	sort(b + 1, b + m + 1);
	for(int i = 1; i <= bl[n]; ++ i){
		memset(s, 0, sizeof(s));
		int div = min(i * block, n);
		pl = div + 1, pr = div;
		ans = 0;
		while(bl[b[pt + 1].l] == i){
			++ pt;
			if(bl[b[pt].l] == bl[b[pt].r]){
				pp[b[pt].time] = js(b[pt].l, b[pt].r);
				continue;
			}
			while(pr < b[pt].r){
				++ pr;
				add(a[pr]);
			}
			long long res = ans;
			while(pl > b[pt].l){
				-- pl;
				add(a[pl]);
			}
			pp[b[pt].time] = ans;
			ans = res;
			while(pl <= div){
				s[a[pl]] --;
				++ pl;
			}
		}
	}
	for(int i = 1; i <= m; ++ i){
		printf("%lld\n", pp[i]);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值