洛谷 P4168 [Violet]蒲公英 题解

洛谷 P4168 [Violet]蒲公英题解

2019-12-25

题意

给定一个含有n个数(1<=n<=4e4)的序列,保证每个数都小于1e9,给定m个询问(1<=m<=5e4),每次询问给定一个区间[l,r],求区间众数,若有多个众数,则输出最小的一个。注意:题目有lastans,要求强制在线

题解

首先先将序列离散化(废话)。注意到n,m都只有不到1e5的规模,容易想到分块系的做法(n的3/2次或5/3次)。但是区间众数这个东西既不满足区间减、只满足区间加,而区间加又是暴力的O(n),因此不可能用传统分块进行维护。因此考虑到我们将每个块的左右端点两两进行匹配视为不同的区间(即每个块的左端点与所有其右侧的块的右端点都视为不同的区间),暴力的算出每个区间的每个数的出现情况和区间众数。每次找到最近的块进行扩展和查询,查询完毕后再还原即可。接下来要考虑的问题是块的大小。不妨设共有x个块。则预处理部分的复杂度是O(n×k2),询问部分的复杂度是O(nm/k)的。很明显,当预处理和查询的复杂度接近时,时间复杂度最小。由于n,m大小相仿,不妨设其复杂度统计,则解方程n×k2=nm/k,解得k=n1/3。所以每个块的大小为n2/3最合适,整个代码的复杂度大约是O(n^5/3),大约在7e7的样子,刚好卡线过。(其实常数蛮大的,最慢的要1.01s,要不是时限是2s就扑街了)

Code

#include<bits/stdc++.h>
using namespace std;
inline int read()//快读
{
	int ret=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
	return w*ret;
}
int n,m;
int a[40010];//原数组
int b[40010];//离散化用数组
map<int,int> mp,mp2;//正向+反向离散化
int t,tot;//t为块大小,tot为块的数目
struct node{
	int type[40010];
	int maxn;
}f[1510];//保存每个区间的信息
int pos[40010];//所在块编号
inline void prework()//预处理
{
	int cnt=0;
	for(int i=1;i<=n;i++)
	if(!mp[b[i]]) mp[b[i]]=++cnt,mp2[cnt]=b[i];
	for(int i=1;i<=n;i++) a[i]=mp[a[i]];
	t=pow(n,2.0/3.0);
	tot=n/t;//计算块的大小和块的数量
	if(tot*t<n) tot++;
	for(int i=1;i<=tot;i++)//预处理所有的区间的每个数出现次数以及众数
	for(int j=i;j<=tot;j++)
	{
		int lp=(i-1)*t+1,rp=min(j*t,n);//确定左右端点
		int p=i*(tot+1)+j;//
		for(int k=lp;k<=rp;k++)//暴力计算区间信息
		{
			f[p].type[a[k]]++;
			if(f[p].type[a[k]]>f[p].type[f[p].maxn]||(f[p].type[a[k]]==f[p].type[f[p].maxn]&&f[p].maxn>a[k])) f[p].maxn=a[k];
		}
	}
	for(int i=1;i<=n;i++) pos[i]=(i-1)/t+1;//计算pos数组
}
inline int solve(int l,int r)//查询
{
	int lp=pos[l],rp=pos[r];
	int p,tmp=0;
	if(lp==rp||lp+1==rp)//若所在块相同或相邻,则无完整的区间信息可以调用,直接一个个计算即可
	{
		p=0;
		for(int i=l;i<=r;i++)
		{
			f[p].type[a[i]]++;
			if(f[p].type[a[i]]>f[p].type[f[p].maxn]||(f[p].type[a[i]]==f[p].type[f[p].maxn]&&f[p].maxn>a[i])) f[p].maxn=a[i];
		}
		int ans=f[p].maxn;
		for(int i=l;i<=r;i++) f[p].type[a[i]]--;
		f[p].maxn=0;
		return ans;
	}
	lp++,rp--;//否则lp和rp中间的部分是完整的块,找到合适的区间信息并以此为基础扩展
	p=lp*(tot+1)+rp,tmp=f[p].maxn;
	for(int i=l;i<=(lp-1)*t;i++)//统计块左部分
	{
		f[p].type[a[i]]++;
		if(f[p].type[a[i]]>f[p].type[f[p].maxn]||(f[p].type[a[i]]==f[p].type[f[p].maxn]&&f[p].maxn>a[i])) f[p].maxn=a[i];
	}
	for(int i=rp*t+1;i<=r;i++)//统计块右部分
	{
		f[p].type[a[i]]++;
		if(f[p].type[a[i]]>f[p].type[f[p].maxn]||(f[p].type[a[i]]==f[p].type[f[p].maxn]&&f[p].maxn>a[i])) f[p].maxn=a[i];
	}
	int ans=f[p].maxn;//计算答案
	for(int i=l;i<=(lp-1)*t;i++) f[p].type[a[i]]--;//还原现场
	for(int i=rp*t+1;i<=r;i++) f[p].type[a[i]]--;
	f[p].maxn=tmp;
	return ans;
}
int lastans=0;
int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;i++) a[i]=read(),b[i]=a[i];
	sort(b+1,b+n+1);
	prework();
	for(int i=1;i<=m;i++)
	{
		int l=read(),r=read();
		l=(l+lastans-1)%n+1,r=(r+lastans-1)%n+1;//记得计算lastans
		if(l>r) swap(l,r);
		lastans=mp2[solve(l,r)];//此处要注意!!!lastans所保存的答案是原数组中的数而非离散化后的
		printf("%d\n",lastans);
	}
	return 0;
}

后记

这道题本来应该30min就A的,结果因为各种愚蠢的锅而跟傻子一样调了好久。最后就因为lastans存了离散化后的答案而差点自闭QAQ比较可惜的是这道题黑掉紫了,否则就做出了人生第一道黑体了啊……最后祝大家圣诞节快乐o(╥﹏╥)o
原文地址:https://www.cnblogs.com/xiaoh105/p/12104542.html