题目链接: 洛谷 P4168 [Violet]蒲公英
前言
一遍过了,巨开心。记得高中的时候的代码,当时调试了三天才过。这次我才不在乎什么常数优化,写代码当然是越稳越好。
Think twice, code once. —— WJMZBMR神犇
#include <cstdio> /// 强制在线的区间众数
#include <algorithm>
using namespace std;
/// 由于这个题细节巨多,所以编程一定要严谨保命,步步思路都要非常清晰才行
const int maxn = 40000 + 5, maxsqrt = 200 + 5; /// 数据最大数量,最大块数目
namespace radix { /// 离散化工具 namespace
int re[maxn]; /// 逆映射
int tmp[maxn]; /// 临时数组
void solve(int* A, int n) { /// 将离散化数据存回 A,并记录离散化映射
for(int i = 1; i <= n; i ++) tmp[i] = A[i];
sort(tmp + 1, tmp + n + 1);
for(int i = 1; i <= n; i ++) {
int val = A[i];
int nval = lower_bound(tmp + 1, tmp + n + 1, A[i]) - tmp; /// 值域是 1 ~ n
A[i] = nval; re[nval] = val; /// A[i] 存入新值
}
}
void test(int n) { /// 用于检测离散化的正确性
for(int i = 1; i <= n; i ++) printf("re[%3d] = %3d, ", i, re[i]);
}
}
namespace blocks { /// 分块大法 ( 这个地方极其容易写错,细节太多 )
const int BlockSize = 200; /// 块的大小为 200
int* Norm; /// 原数列指针
int N; /// 总元素个数
int BlockId[maxn]; /// 记录每个位置的 块 编号,末尾的值均为 0,最后不足一块的部分也算做一块
int Pre[maxsqrt][maxn]; /// Pre[i][j] 表示前 i 块中 j 出现的次数
int Seg[maxsqrt][maxsqrt]; /// Seg[i][j] 表示 第 i 块 到第 j 块的众数
int Begin[maxsqrt]; /// 记录每个块的开始
int End [maxsqrt]; /// 记录每个块的结束位置
int Bcnt = 0; /// 统计总块数
int tmp[maxn]; /// 万能数据桶:记得用后清空 ( 清空时务必考虑时间复杂度 )
inline int ocur(int LB, int RB, int color) { /// 返回颜色 color 在第 blockId 块中出现的次数
return Pre[RB][color] - Pre[LB - 1][color]; /// 差分
}
inline int isEnd(int pos) { /// 判断某个位置是否是某个块的结尾
return BlockId[pos] != BlockId[pos + 1] ? BlockId[pos] : 0; /// 不是结尾返回 0,否则返回块的编号
}
void init(int* norm, int n) { /// 初始化构造
Norm = norm; N = n;
for(int i = 1; i <= n; i ++) {
BlockId[i] = (i-1)/BlockSize + 1; /// 块 编号从 1 开始向上增长
End[BlockId[i]] = i; /// 覆盖法记录每个块的结束位置
if(BlockId[i] != BlockId[i-1]) Begin[BlockId[i]] = i, Bcnt ++; /// 记录每个块的开始位置
}
/// 统计 Pre 数组
for(int i = 1; i <= n; i ++) Pre[BlockId[i]][Norm[i]] ++; /// 先统计每个块内部的每种颜色出现的次数
for(int j = 1; j <= n; j ++) /// 再对这 N 个长度为 Bcnt 的向量求前缀和
for(int i = 2; i <= Bcnt; i ++) Pre[i][j] += Pre[i-1][j];
/// 统计 Seg 数组
for(int i = 1; i <= Bcnt; i ++) { /// 注意:此时 tmp 数据桶是空的
int now = 0, cnt = 0;
for(int pos = Begin[i]; pos <= N; pos ++) {
int color = Norm[pos];
tmp[color] ++;
if((tmp[color] > cnt) || (tmp[color] == cnt && color < now)) { /// 时刻更新最小众数的值
now = color;
cnt = tmp[color];
}
int id = isEnd(pos); /// 如果当前位置恰好是某个块的结尾
if(id) Seg[i][id] = now; /// 当前的众数,就是第 i 块 到 第 id 块的区间众数
}
/// 此时 tmp 数组中储存着 Begin[i] ~ N 的各种颜色的出现次数数据
for(int pos = Begin[i]; pos <= N; pos ++) tmp[Norm[pos]] --; /// 利用回滚法清空数据桶
/// 此时 tmp 数据桶是空的
}
}
int lastans = 0;
void regain(int& l, int& r) { /// 重新获得在线数据
l = (l + lastans - 1) % N + 1;
r = (r + lastans - 1) % N + 1; /// lastans>=1 , 1<=l,r<=n
if(l > r) swap(l, r); /// 保证 l <= r
}
int exist[maxn]; /// 是否统计数据桶:exist[i] = 1 表示 已经统计了 颜色 i 的大区间信息
int solve(int Lpos, int Rpos) { /// 记得 re 离散化
//printf("Lpos = %d, Rpos = %d\n", Lpos, Rpos);
int now = 0, cnt = 0;
if(BlockId[Rpos] - BlockId[Lpos] <= 1) { /// 区间非常的短,暴力统计众数,此时 tmp 数据桶为空
for(int pos = Lpos; pos <= Rpos; pos ++) {
int color = Norm[pos]; /// 当前位置的颜色
tmp[color] ++;
if((tmp[color] > cnt) || (tmp[color] == cnt && color < now)) { /// 更新当前的众数
now = color;
cnt = tmp[color];
}
}
/// 现在 tmp 储存着 Lpos ~ Rpos 之间的数据桶信息
for(int pos = Lpos; pos <= Rpos; pos ++) tmp[Norm[pos]] --; /// 利用回滚清空数据桶
}else {
/// 我们只关注两侧零星的数据以及中间数据段的众数
int LB = BlockId[Lpos] + 1;
int RB = BlockId[Rpos] - 1; /// 由于 BlockId[Rpos] - BlockId[Lpos] >= 2 所以 RB - LB >= 0
int mid = Seg[LB][RB]; /// 中间部分的众数
/// 注意: 现在 exist 数据桶是空的, tmp 数据桶也是空的
exist[mid] = 1; tmp[mid] += ocur(LB, RB, mid);
now = mid; cnt = tmp[mid]; /// 初始值
for(int i = Lpos; BlockId[i] == BlockId[Lpos]; i ++) { /// 统计左侧多余部分
int color = Norm[i];
if(!exist[color]) exist[color] = 1, tmp[color] += ocur(LB, RB, color);
tmp[color] ++;
if((tmp[color] > cnt) || (tmp[color] == cnt && color < now)) { /// 更新当前的众数
now = color;
cnt = tmp[color];
}
}
for(int i = Begin[BlockId[Rpos]]; i <= Rpos; i ++) { /// 统计右侧多余部分
int color = Norm[i];
if(!exist[color]) exist[color] = 1, tmp[color] += ocur(LB, RB, color);
tmp[color] ++;
if((tmp[color] > cnt) || (tmp[color] == cnt && color < now)) { /// 更新当前的众数
now = color;
cnt = tmp[color];
}
}
/// 此时,一定要清空 tmp 数据桶和 exist 数据桶
exist[mid] = 0; tmp[mid] = 0;
for(int i = Lpos; BlockId[i] == BlockId[Lpos]; i ++) exist[Norm[i]] = tmp[Norm[i]] = 0;
for(int i = Begin[BlockId[Rpos]]; i <= Rpos; i ++) exist[Norm[i]] = tmp[Norm[i]] = 0;
/// 直接暴力清空 tmp 和 exist 就行 ( 回滚什么的是闲得无聊吧 )
}
return lastans = radix :: re[now];
}
}
int norm[maxn]; /// 储存离散化后的原数列
int main() {
int n, m; scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++) scanf("%d", &norm[i]);
radix :: solve(norm, n); /// 离散化
//radix :: test(n);
blocks :: init(norm, n); /// 初始化分块数据结构
for(int i = 1; i <= m; i ++) {
int l, r; scanf("%d%d", &l, &r);
blocks :: regain(l, r); /// 记得开 long long ( 手动 @NOI2018 归程 )
printf("%d\n", blocks :: solve(l, r));
}
return 0;
}