在线静态区间众数查询

题目链接: 洛谷 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; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值