区间众数 / 蒲公英
题目链接:ybt金牌导航6-3-1 / luogu P4168
题目大意
给出一个序列,每次求一个区间的众数。
众数:出现次数最多的数,如果有多个数字,则选小的那个。
强制在线。
思路
这道题是分块的例题。
分块就是把数列分成很多个块,然后这些块是可以整个算的。
这样的话,如果每次你要询问区间,你可以把它分成三个部分:前面多出来的,后面多出来的,中间若干个区间。
前面后面多出来的我们可以直接暴力,然后中间就可以弄个前缀和或者什么的快速地求出来。
然后我们想想分块,每个块的大小是多少最优。
你既要顾忌块的个数,还要顾忌块的大小,那我们就让这两个尽可能相同,那就是 (sqrt{n}) 的大小,分成大概 (sqrt{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('
');
}
return 0;
}