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

题面

蒲公英

解析

前言

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

暴力做法

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

分块做法

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

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

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

写完交上去于是发现你 \(TLE\) 了,所以我们还要找找哪里能优化。
初始化的 \(f[i][j]\) 是没要优化也不好优化的,发现每次询问里的区间两边的暴力很浪费时间,每次都是 \(O(n)\) 的。所以我们可以用二分查找的方式降低时间复杂度。
\(vector\)\(a[i]\) 每次在序列中出现的下标,因为 \(a[i] \leq 1e9\) 所以显然还要离散化。然后进入正题,如何二分查找 \(a[i]\)\(L\) ~ \(R\) 中的出现次数。
对于本题的样例,\(2\) 对应的 \(vector\) 里存的是 {2,4,6},我们要找 \(2\)\(3\) ~ \(6\) 中 出现的次数,找到最后一个小于等于 \(R:6\) 的数 \(6\) ,第一个大于等于 \(L:3\) 的数 \(3\),两者下标相减,以此就能到区间 \(L\) ~ \(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;
}
posted @ 2021-05-29 19:59  init-神眷の樱花  阅读(57)  评论(0)    收藏  举报