[Violet]蒲公英 分块 + 二分查找 详解

题面

蒲公英

解析

前言

一道从黑掉到紫的阴间分块题。。。
显然是一道求静态区间众数题。
因为区间众数不具有可加性,用线段树,树状数组的数据结构并不好维护,再加上本题的数据范围和两秒的限制,所以本题可以分块。

暴力做法

瞎搞能拿70 ~ 80 分,这里就不赘述了。

分块做法

正常分块,不过维护的信息变为从 块 i 块_i i ~ 块 j 块_j j 的众数是哪个,我们用 f [ i ] [ j ] f[i][j] f[i][j] 来表示。
假设已经知道这个信息怎么维护了,那么对于每个询问 L L L ~ R R R ,答案只会有三种情况:

  • 中间的连续的块里的众数( f [ p o s [ L ] + 1 ] [ p o s [ R ] − 1 ] f[pos[L] + 1][pos[R] - 1] f[pos[L]+1][pos[R]1]
  • 左边剩余的数里的众数。( p o s [ i ] pos[i] pos[i] 表示 i i i 处于哪一个块)
  • 右边剩余的数里的众数。(暴力求)

以上的做法就完全是分块的味道了。
下面我们来说 f [ i ] [ j ] f[i][j] f[i][j] 的求法:
其实就是两层 f o r for for 循环暴力枚举。。。

写完交上去于是发现你 T L E TLE TLE 了,所以我们还要找找哪里能优化。
初始化的 f [ i ] [ j ] f[i][j] f[i][j] 是没要优化也不好优化的,发现每次询问里的区间两边的暴力很浪费时间,每次都是 O ( n ) O(n) O(n) 的。所以我们可以用二分查找的方式降低时间复杂度。
v e c t o r vector vector a [ i ] a[i] a[i] 每次在序列中出现的下标,因为 a [ i ] ≤ 1 e 9 a[i] \leq 1e9 a[i]1e9 所以显然还要离散化。然后进入正题,如何二分查找 a [ i ] a[i] a[i] L L L ~ R R R 中的出现次数。
对于本题的样例, 2 2 2 对应的 v e c t o r vector vector 里存的是 {2,4,6},我们要找 2 2 2 3 3 3 ~ 6 6 6 中 出现的次数,找到最后一个小于等于 R : 6 R:6 R:6 的数 6 6 6 ,第一个大于等于 L : 3 L:3 L3 的数 3 3 3,两者下标相减,以此就能到区间 L L L ~ R R R 中任意数的出现次数。

代码

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

using namespace std;

const int N = 4e4 + 5;
int a[N],r[N],pos[N],f[1000][1000],cnt[N],n,m,block;
vector<int> g[N];
void init() {
	block = max(1,(int)(n / sqrt(m * log2(n))));
	for(int i = 1; i <= n; i++)
		pos[i] = (i - 1) / block + 1;
}
inline int Get(int l,int r,int val) {
	return upper_bound(g[val].begin(),g[val].end(),r) - lower_bound(g[val].begin(),g[val].end(),l); 
}
void work(int x) {
	memset(cnt,0,sizeof(cnt));
	int maxn = -1,ans = 0;
	for(int i = (x - 1) * block + 1; i <= n; i++) {
		cnt[a[i]]++; 
		if(cnt[a[i]] > maxn || (cnt[a[i]] == maxn && a[i] < ans)) {
			maxn = cnt[a[i]];
			ans = a[i];
		}
		f[x][pos[i]] = ans;
	}
}
int query(int l,int r) {
	int ans = f[pos[l] + 1][pos[r] - 1],maxn = Get(l,r,ans);
	int up = min(r,pos[l] * block);
	for(int i = l; i <= up; i++) {
		int cnt = Get(l,r,a[i]);
		if(cnt > maxn || (cnt == maxn && a[i] < ans)) {
			maxn = cnt;
			ans = a[i];
		} 
	}
	if(pos[l] == pos[r]) return ans;
	for(int i = (pos[r] - 1) * block + 1; i <= r; i++) {
		int cnt = Get(l,r,a[i]);
		if(cnt > maxn || (cnt == maxn && a[i] < ans)) {
			maxn = cnt;
			ans = a[i];
		}
	}
	return ans;
}
inline int read() {
	int x = 0,flag = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-')flag = -1;ch = getchar();}
	while(ch >='0' && ch <='9'){x = (x << 3) + (x << 1) + ch - 48;ch = getchar();}
	return x * flag; 
}
int main() {
	n = read(),m = read();
	for(int i = 1; i <= n; i++) r[i] = a[i] = read();
	sort(r + 1,r + 1 + n);
	int tot = unique(r + 1,r + 1 + n) - (r + 1);
	for(int i = 1; i <= n; i++) {
		a[i] = lower_bound(r + 1,r + 1 + tot,a[i]) - r;
		g[a[i]].push_back(i); 
	} init();
	for(int i = 1; i <= pos[n]; i++) work(i);
	for(int i = 1,x = 0; i <= m; i++) {
		int L = read(),R = read();
		L = (L + x - 1) % n + 1;
		R = (R + x - 1) % n + 1;
		if(L > R) swap(L,R);
		x = r[query(L,R)]; printf("%d\n",x);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值