《P4168 [Violet] 蒲公英题解》
一、题目概述
- 核心任务:给定一个长度为 (n) 的蒲公英种类序列 ({a_1, a_2, \cdots, a_n}),需要对 (m) 次询问做出回应。每次询问一个区间 ([l, r]),要找出该区间内出现次数最多的蒲公英种类编号;若多种蒲公英出现次数相同,则输出编号最小的那个。并且要求算法在线处理询问,即每次询问都要基于之前询问的结果来处理当前输入。
- 输入处理:输入包含 (n)(蒲公英数量)、(m)(询问次数)以及序列 ({a_i})。询问区间 ((l_0, r_0)) 是加密的,需根据上次询问结果 (x)(首次询问 (x = 0))进行解密,通过公式 (l = ((l_0 + x - 1) \bmod n) + 1) 和 (r = ((r_0 + x - 1) \bmod n) + 1) 得到真实询问区间 ([l, r]),若 (l > r),则交换 (l) 和 (r)。
二、代码思路剖析
1. 全局变量
int a[40010], b[40010];
int L[410], R[410];
int pos[40010];
int p[410][410];
int tong[40010];
int f[410][40010];
int n, m;
a
数组:存储输入的蒲公英种类编号。b
数组:辅助离散化,在离散化过程中存储a
数组的副本并进行排序。L
和R
数组:记录分块后的每一块的左右边界。pos
数组:记录每个位置所属的块编号。p
数组:用于记录块与块之间的统计信息,方便快速查询跨块区间的结果。tong
数组:临时统计蒲公英种类的出现次数。f
数组:记录前缀和信息,辅助计算不同块内蒲公英种类的出现次数。n
和m
:分别表示蒲公英数量和询问次数。
2. 输入输出优化函数
int in() {
int k = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') {
if (c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9') k = k * 10 + c - '0', c = getchar();
return k * f;
}
void out(int x) {
if (x < 0) putchar('-'), x = -x;
if (x < 10) putchar(x + '0');
else out(x / 10), putchar(x % 10 + '0');
}
in
函数:通过getchar
逐字符读取输入,实现比scanf
更快的整数输入读取,同时处理正负号。out
函数:通过递归方式实现比printf
更快的整数输出。
3. 读取输入函数
void read() {
n = in();
m = in();
}
使用 in
函数读取蒲公英数量 n
和询问次数 m
。
4. 离散化函数
void lisanhua() {
for (int i = 1; i <= n; i++) {
a[i] = in();
b[i] = a[i];
}
sort(b + 1, b + n + 1);
tot = unique(b + 1, b + n + 1) - b - 1;
for (int i = 1; i <= n; i++) {
a[i] = lower_bound(b + 1, b + tot + 1, a[i]) - b;
}
}
- 首先将输入的蒲公英种类编号读入
a
数组,并复制到b
数组。 - 对
b
数组排序,然后用unique
函数去除重复元素,tot
记录去重后的元素个数。 - 通过
lower_bound
函数将a
数组中的每个元素映射到离散化后的编号,以减小数据范围,方便后续处理。
5. 分块预处理函数
void yu() {
int len = sqrt(n);
int len_ll = len;
// 分块,确定每块边界
for (int i = 1; i <= len; i++) {
L[i] = (i - 1) * len + 1;
R[i] = i * len;
}
if (R[len] < n) {
while (n - R[len] >= len_ll) {
len++;
L[len] = R[len - 1] + 1;
R[len] = R[len - 1] + len_ll;
}
if (R[len] < n) {
len++;
L[len] = R[len - 1] + 1;
R[len] = n;
}
}
// 记录位置所属块编号,统计块内蒲公英出现次数前缀和
for (int i = 1; i <= len; i++) {
for (int j = L[i]; j <= R[i]; j++) {
pos[j] = i;
tong[a[j]]++;
}
for (int j = 1; j <= tot; j++) {
f[i][j] = f[i - 1][j] + tong[j];
}
for (int j = L[i]; j <= R[i]; j++) {
tong[a[j]] = 0;
}
}
// 预处理块与块之间的信息
for (int i = 1; i <= len; i++) {
int res = 0;
for (int j = i; j <= len; j++) {
for (int k = L[j]; k <= R[j]; k++) {
tong[a[k]]++;
if (tong[a[k]] > tong[res] || (tong[a[k]] == tong[res] && a[k] < res)) {
res = a[k];
}
}
p[i][j] = res;
}
memset(tong, 0, sizeof(tong));
}
}
- 分块操作:根据序列长度 (n) 计算块大小
len
(取 (\sqrt{n})),确定每一块的左右边界L[i]
和R[i]
,并处理最后一块可能不足len
大小的情况。 - 块内预处理:遍历每一块,记录每个位置所属的块编号
pos[j]
,统计每块内每种蒲公英的出现次数tong
,并据此计算前缀和f
,之后将tong
数组清零以便后续使用。 - 块间预处理:通过两层循环遍历块与块之间的范围,统计每个块间组合内出现次数最多且编号最小的蒲公英种类编号,记录到
p
数组。
6. 询问处理函数
int take(int l, int r) {
int l1 = pos[l], r1 = pos[r];
if (r1 - l1 <= 1) {
int res = 0;
for (int i = l; i <= r; i++) {
tong[a[i]]++;
if (tong[a[i]] > tong[res] || (tong[a[i]] == tong[res] && res > a[i])) {
res = a[i];
}
}
for (int i = l; i <= r; i++) {
tong[a[i]] = 0;
}
return res;
} else {
int res = 0, sum = 0;
// 统计两端非完整块的蒲公英出现次数
for (int i = l; i <= R[l1]; i++) {
tong[a[i]]++;
}
for (int i = L[r1]; i <= r; i++) {
tong[a[i]]++;
}
// 计算两端非完整块内的结果
for (int i = l; i <= R[l1]; i++) {
if (tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]] > sum) {
sum = tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]];
res = a[i];
} else if (tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]] == sum) {
res = min(res, a[i]);
}
}
for (int i = L[r1]; i <= r; i++) {
if (tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]] > sum) {
sum = tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]];
res = a[i];
} else if (tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]] == sum) {
res = min(res, a[i]);
}
}
// 比较中间完整块的结果
if (sum < f[r1 - 1][ p[l1 + 1][r1 - 1] ] - f[l1][ p[l1 + 1][r1 - 1] ]) {
sum = f[r1 - 1][ p[l1 + 1][r1 - 1] ] - f[l1][ p[l1 + 1][r1 - 1] ];
res = p[l1 + 1][r1 - 1];
} else if (sum == f[r1 - 1][ p[l1 + 1][r1 - 1] ] - f[l1][ p[l1 + 1][r1 - 1] ]) {
res = min(res, p[l1 + 1][r1 - 1]);
}
// 清零统计数组
for (int i = l; i <= R[l1]; i++) tong[a[i]] = 0;
for (int i = L[r1]; i <= r; i++) tong[a[i]] = 0;
return res;
}
}
- 情况一:询问区间在相邻或同一一块内:直接遍历区间内元素,用
tong
数组统计每种蒲公英出现次数,更新出现次数最多且编号最小的蒲公英种类编号res
,最后将tong
数组清零并返回res
。 - 情况二:询问区间跨越多个块:
- 先统计左右两端非完整块内每种蒲公英的出现次数。
- 结合前缀和数组
f
,计算两端非完整块内出现次数最多且编号最小的蒲公英种类编号res
及其出现次数sum
。 - 比较中间完整块的结果(通过
p
数组获取),若中间完整块内某种蒲公英出现次数更多或相等且编号更小,则更新res
。 - 最后将
tong
数组清零并返回res
。
7. 主询问函数
void ask() {
int ans = 0;
while (m--) {
int l0, r0;
l0 = in();
r0 = in();
int l = (l0 + ans - 1) % n + 1;
int r = (r0 + ans - 1) % n + 1;
if (l > r) {
swap(l, r);
}
ans = b[take(l, r)];
out(ans);
putchar('\n');
}
}
- 循环处理 (m) 次询问,每次读取加密的询问区间 ((l_0, r_0))。
- 根据上次询问结果
ans
解密得到真实询问区间 ([l, r]),若 (l > r) 则交换它们。 - 调用
take
函数获取询问区间内出现次数最多且编号最小的蒲公英种类编号,将其还原为离散化前的编号(通过b
数组),输出结果并换行。
8. 主函数
int main() {
read();
lisanhua();
yu();
ask();
return 0;
}
依次调用 read
函数读取输入,lisanhua
函数进行离散化,yu
函数进行分块预处理,ask
函数处理询问并输出结果。
三、总体代码
#include <bits/stdc++.h>
using namespace std;
int a[40010], b[40010];
int L[410], R[410];
int pos[40010];
int p[410][410];
int tong[40010];
int f[410][40010];
int n, m;
int in()
{
int k=0,f=1;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9')k=k*10+c-'0',c=getchar();
return k*f;
}
void out(int x)
{
if(x<0)putchar('-'),x=-x;
if(x<10)putchar(x+'0');
else out(x/10),putchar(x%10+'0');
}
void read()
{
n = in();
m = in();
}
int tot;
void lisanhua()
{
for (int i = 1; i <= n; i++)
{
a[i] = in();
b[i] = a[i];
}
sort(b + 1, b + n + 1);
tot = unique(b + 1, b + n + 1) - b - 1;
for (int i = 1; i <= n; i++)
{
a[i] = lower_bound(b + 1, b + tot + 1, a[i]) - b;
}
}
void yu()
{
int len = sqrt(n);
int len_ll = len;
for (int i = 1; i <= len; i++)
{
L[i] = (i - 1) * len + 1;
R[i] = i * len;
}
if (R[len] < n)
{
while (n - R[len] >= len_ll)
{
len++;
L[len] = R[len - 1] + 1;
R[len] = R[len - 1] + len_ll;
}
if (R[len] < n)
{
len++;
L[len] = R[len - 1] + 1;
R[len] = n;
}
}
for (int i = 1; i <= len; i++)
{
for (int j = L[i]; j <= R[i]; j++)
{
pos[j] = i;
tong[a[j]]++;
}
for (int j = 1; j <= tot; j++)
{
f[i][j] = f[i - 1][j] + tong[j];
}
for (int j = L[i]; j <= R[i]; j++)
{
tong[a[j]] = 0;
}
}
for (int i = 1; i <= len; i++)
{
int res = 0;
for (int j = i; j <= len; j++)
{
for (int k = L[j]; k <= R[j]; k++)
{
tong[a[k]]++;
if (tong[a[k]] > tong[res] || (tong[a[k]] == tong[res] && a[k] < res))
{
res = a[k];
}
}
p[i][j] = res;
}
memset(tong, 0, sizeof(tong));
}
}
int take(int l, int r)
{
int l1 = pos[l], r1 = pos[r];
if (r1 - l1 <= 1)
{
int res = 0;
for (int i = l; i <= r; i++)
{
tong[a[i]]++;
if (tong[a[i]] > tong[res] || (tong[a[i]] == tong[res] && res > a[i]))
{
res = a[i];
}
}
for (int i = l; i <= r; i++)
{
tong[a[i]] = 0;
}
return res;
}
else
{
int res = 0, sum = 0;
for (int i = l; i <= R[l1]; i++)
{
tong[a[i]]++;
}
for (int i = L[r1]; i <= r; i++)
{
tong[a[i]]++;
}
for (int i = l; i <= R[l1]; i++)
{
if (tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]] > sum)
{
sum = tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]];
res = a[i];
}
else if (tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]] == sum)
{
res = min(res, a[i]);
}
}
for (int i = L[r1]; i <= r; i++)
{
if (tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]] > sum)
{
sum = tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]];
res = a[i];
}
else if (tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]] == sum)
{
res = min(res, a[i]);
}
}
if (sum < f[r1 - 1][ p[l1 + 1][r1 - 1] ] - f[l1][ p[l1 + 1][r1 - 1] ])
{
sum = f[r1 - 1][ p[l1 + 1][r1 - 1] ] - f[l1][ p[l1 + 1][r1 - 1] ];
res = p[l1 + 1][r1 - 1];
}
else if (sum == f[r1 - 1][ p[l1 + 1][r1 - 1] ] - f[l1][ p[l1 + 1][r1 - 1] ])
{
res = min(res, p[l1 + 1][r1 - 1]);
}
for (int i = l; i <= R[l1]; i++) tong[a[i]] = 0;
for (int i = L[r1]; i <= r; i++) tong[a[i]] = 0;
return res;
}
}
void ask()
{
int ans = 0;
while(m--)
{
int l0, r0;
l0 = in();
r0 = in();
int l = (l0 + ans - 1) % n + 1;
int r = (r0 + ans - 1) % n + 1;
if (l > r)
{
swap(l, r);
}
ans = b[take(l, r)];
out(ans);
putchar('\n');
}
}
int main()
{
read();
lisanhua();
yu();
ask();
return 0;
}
注:以防大家看着代码不便,我把代码格式改了一下