【ybt金牌导航6-3-1】【luogu P4168】区间众数 / 蒲公英 / 分块例题

区间众数 / 蒲公英

题目链接:ybt金牌导航6-3-1 / luogu P4168

题目大意

给出一个序列,每次求一个区间的众数。
众数:出现次数最多的数,如果有多个数字,则选小的那个。
强制在线。

思路

这道题是分块的例题。

分块就是把数列分成很多个块,然后这些块是可以整个算的。
这样的话,如果每次你要询问区间,你可以把它分成三个部分:前面多出来的,后面多出来的,中间若干个区间。
前面后面多出来的我们可以直接暴力,然后中间就可以弄个前缀和或者什么的快速地求出来。

然后我们想想分块,每个块的大小是多少最优。
你既要顾忌块的个数,还要顾忌块的大小,那我们就让这两个尽可能相同,那就是 n \sqrt{n} n 的大小,分成大概 n \sqrt{n} n 段。
(是大概是因为最后可以会多出来一点,但是这一点无所谓,算就是了)

那这道题也是这样子,但是你看到数字很大,然后决定要记录每个数字在区间中出现的次数。
那就把数字离散化一下,然后就按着上面的方法做。

然后具体可以看一下代码。

代码

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

using namespace std;

struct node {
	int x, num, val;
}a[40001];
int n, m, dy[40001], tot, K;
int num[201][40001], x, y, ans;
int sum[40001], maxn, maxx;

bool cmp(node x, node y) {
	return x.x < y.x;
}

bool cmp_b(node x, node y) {
	return x.num < y.num;
}

int work(int x, int y) {
	maxn = -1;
	memset(sum, 0, sizeof(sum));
	
	while (x % K != 0 && x <= y) {//处理前面的多出来的
		sum[a[x].val]++;
		if (sum[a[x].val] > maxn) {
			maxn = sum[a[x].val];
			maxx = a[x].val;
		}
		else if (sum[a[x].val] == maxn && a[x].val < maxx) {
			maxx = a[x].val;
		}
		x++;
	}
	while (y % K != K - 1 && x <= y) {//处理后面多出来的
		sum[a[y].val]++;
		if (sum[a[y].val] > maxn) {
			maxn = sum[a[y].val];
			maxx = a[y].val;
		}
		else if (sum[a[y].val] == maxn && a[y].val < maxx) {
			maxx = a[y].val;
		}
		y--;
	}
	
	if (x > y) {//没有中间的大块
		return dy[maxx];
	}
	
	for (register int i = 0; i <= tot; i++) {//枚举数
		sum[i] += num[y / K][i] - ((x / K - 1 < 0) ? 0 : num[x / K - 1][i]);
		//利用前缀和算出大区间中有多少个这个数
		if (sum[i] > maxn) {
			maxn = sum[i];
			maxx = i;
		}
		else if (sum[i] == maxn && i < maxx) {
			maxx = i;
		}
	}
	
	return dy[maxx];
}

int read() {
	int re = 0;
	char c = getchar();
	while (c < '0' || c > '9') c = getchar();
	while (c >= '0' && c <= '9') {
		re = re * 10 + c - '0';
		c = getchar();
	}
	return re;
}

void write(int now) {
	if (now > 9) write(now / 10);
	putchar(now % 10 + '0');
}

int main() {
	n = read();
	m = read();
	for (register int i = 0; i < n; i++) {
		a[i].x = read();
		a[i].num = i;
	}
	
	sort(a, a + n, cmp);//离散化
	a[0].val = 0;
	dy[tot] = a[0].x;
	for (register int i = 1; i < n; i++) {
		if (a[i].x != a[i - 1].x) {
			tot++;
			dy[tot] = a[i].x;
		}
		a[i].val = tot;
	}
	sort(a, a + n, cmp_b);
	
	K = floor(sqrt(n));//分块
	for (register int i = 0; i < n; i++) {//算每个块每个数字的数量
		num[i / K][a[i].val]++;
	}
	for (register int i = 1; i < n / K; i++)//前缀和算1~i个块中数字j的数量有多少个
		for (register int j = 0; j <= tot; j++)
			num[i][j] += num[i - 1][j];
	
	for (register int i = 1; i <= m; i++) {
		x = read();
		y = read();
		x = ((x + ans - 1) % n) + 1;
		y = ((y + ans - 1) % n) + 1;
		if (x > y) swap(x, y);//解码
		
		x--;//因为我是从0编号,所以编号要减一
		y--;
		
		ans = work(x, y);
		
		write(ans);
		putchar('\n');
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值