洛谷 P5048 [Ynoi2019 模拟赛] Yuno loves sqrt technology III

题目描述

给你一个长为 n 的序列 am 次询问,每次查询一个区间的众数的出现次数强制在线

\(1≤n,m≤5×10^5,0 \leq a_i\leq 10^9,0≤a_i≤10^9。\)

Solution

An easy problem

本文将以从洛谷 P4168 [Violet]蒲公英的转换视角来解决这道题。蒲公英这道题不仅数据范围友好,题目背景也不错。

以下默认块大小为\(\sqrt n\).

前置知识:基本的分块,求众数。

先来讨论一下蒲公英那道题是怎么做的:

首先离散化。

预处理出从第\(i\)块到第\(j\)块中的众数\(zs_{i,j}\)。可以枚举\(i\),枚举\(j\),再枚举块内元素开一个桶统计。时间复杂度\(O(n\sqrt{n})\),空间复杂度\(O(n)+O(\sqrt n\times \sqrt n)=O(n)\)。

预处理出第\(1\)块到第\(i\)块这个区间\([1,i]\)中数字\(x\)出现的次数\(cnt_{i,x}\)。可以枚举\(i\)后,先将\((\forall x)cnt_{i-1,x}\)拷贝到\(cnt_{i,x}\),再统计当前块\(i\)。时间复杂度\(O(\sqrt n\times (n+\sqrt n))=O(n\sqrt n)\),空间复杂度\(O(n\sqrt n)\)。

对于每一个询问\((l,r)\),讨论其是否在一个块内。若是同块,就使用桶暴力统计。若不在一个块内,则将其拆为两个散块和一个整块。

首先答案\(ans=zs_{i,j}\),再向两边拓展,看看散块内的数\(x\)是否可能成为比\(ans\)出现次数更大的数。具体的,整块中\(x\)出现的次数可以用之前预处理出的\(cnt_{j,x}-cnt_{i-1,x}\)这种形式表示。散块中的\(x\)使用桶来统计即可。

询问的时间复杂度为\(O(\sqrt n+2\times \sqrt n)=O(\sqrt n)\)。

Code

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<queue>
#include<vector>
#include<limits.h>
#define IL inline
#define re register
#define LL long long
#define ULL unsigned long long
#ifdef TH
#define debug printf("Now is %d\n",__LINE__);
#else
#define debug
#endif
using namespace std; template<class T>inline void read(T&x)
{
char ch=getchar();
int fu;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') fu=-1,ch=getchar();
x=ch-'0';ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
x*=fu;
}
inline int read()
{
int x=0,fu=1;
char ch=getchar();
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') fu=-1,ch=getchar();
x=ch-'0';ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*fu;
}
int G[55];
template<class T>inline void write(T x)
{
int g=0;
if(x<0) x=-x,putchar('-');
do{G[++g]=x%10;x/=10;}while(x);
for(int i=g;i>=1;--i)putchar('0'+G[i]);putchar('\n');
}
#define N 40010
#define S 300
int n,m,s,block,L[N],R[N],belong[N],zs[S][S],cnt[N],a[N],f[S][N],sum[N];
int rys[N];
int lastans;
map<int,int>ys;
int main()
{
n=read();m=read();
s=sqrt(n);
block=n/s;
for(int i=1;i<=s;i++)
{
L[i]=n/s*(i-1)+1;
R[i]=n/s*i;
}
R[s]=n;
for(int i=1;i<=s;i++)
{
for(int j=L[i];j<=R[i];j++)
{
belong[j]=i;
}
}
for(int i=1;i<=n;i++)
{
cin>>a[i];
ys[a[i]]=0;
}
int rank=1;
for(map<int,int>::iterator it=ys.begin();it!=ys.end();it++)
{
rys[rank]=it->first;
it->second=rank;
rank++;
}
for(int i=1;i<=n;i++) a[i]=ys[a[i]];
// for(int i=1;i<=n;i++) cout<<a[i]<<endl;
for(int i=1;i<=s;i++)
{
memset(cnt,0,sizeof(cnt));
for(int j=i;j<=s;j++)
{
zs[i][j]=zs[i][j-1];
for(int k=L[j];k<=R[j];k++)
{
cnt[a[k]]++;
if(cnt[a[k]]>cnt[zs[i][j]] || (cnt[a[k]]==cnt[zs[i][j]]&&a[k]<zs[i][j])) zs[i][j]=a[k];
}
}
}
for(int i=1;i<=s;i++)
{
for(int j=1;j<=ys.size();j++) f[i][j]=f[i-1][j];
for(int j=L[i];j<=R[i];j++)
{
f[i][a[j]]++;
}
}
for(int t=1,x,y;t<=m;t++)
{
cin>>x>>y;
x=(x+lastans-1)%n+1;
y=(y+lastans-1)%n+1;
if(x>y) x^=y^=x^=y;
LL ans=0;
if(belong[y]-belong[x]<=3)
{
for(int i=x;i<=y;i++) sum[a[i]]=0;
for(int i=x;i<=y;i++)
{
sum[a[i]]++;
if(sum[a[i]]>sum[ans] || (sum[a[i]]==sum[ans]&&a[i]<ans)) ans=a[i];
}
}
else
{
ans=zs[belong[x]+1][belong[y]-1];
sum[ans]=0;
for(int i=x;i<=R[belong[x]];i++)
{
sum[a[i]]=0;
}
for(int i=L[belong[y]];i<=y;i++)
{
sum[a[i]]=0;
}
for(int i=x;i<=R[belong[x]];i++)
{
sum[a[i]]++;
if(f[belong[y]-1][a[i]]-f[belong[x]][a[i]]+sum[a[i]]>f[belong[y]-1][ans]-f[belong[x]][ans]+sum[ans] || (f[belong[y]-1][a[i]]-f[belong[x]][a[i]]+sum[a[i]]==f[belong[y]-1][ans]-f[belong[x]][ans]+sum[ans]&&a[i]<ans)) ans=a[i];
}
for(int i=L[belong[y]];i<=y;i++)
{
sum[a[i]]++;
if(f[belong[y]-1][a[i]]-f[belong[x]][a[i]]+sum[a[i]]>f[belong[y]-1][ans]-f[belong[x]][ans]+sum[ans] || (f[belong[y]-1][a[i]]-f[belong[x]][a[i]]+sum[a[i]]==f[belong[y]-1][ans]-f[belong[x]][ans]+sum[ans]&&a[i]<ans)) ans=a[i];
}
}
cout<<(lastans=rys[ans])<<endl;
}
return 0;
}

回到正题

蒲公英的数据范围十分友好:\(n\le 4\times 10^4,m\le 10^5\)。所以我们完全能够开下\(cnt_{s,n}\)。

但是P5048 [Ynoi2019 模拟赛] Yuno loves sqrt technology III的数据范围就不那么友好了:\(n,m\le 5\times 10^5\)。

这两道题有许多的共同点:区间众数,无修改,要离散化,数据范围看上去是根号做法的。

但是也有一些不同点:无法预处理前缀块的桶(因为空间开不下),求的是众数出现的次数而非众数是谁(其实差不多,但是求出现次数的话需要处理的信息可以少了点)

无法开桶,那么可以用另一种方式表达这个桶吗?可以的。

首先离散化,预处理\(zs_{i,j}\)就不必多费阐述了吧

使用\(n\)个\(vector\)中存放下标,\(vector_{x,i}\)表示数x第i次出现的下标。由于是\(vector\),所以总空间依然是\(O(n)\)的。

再记\(p_i\)表示\(i\)这位上的数\(x\)在其\(vector_{x}\)中在第几位。

显然预处理这个的时间复杂度是\(O(n)\)的。

对于每个询问,我们还是类似于上面的那样,同块暴力统计即可;

否则依然将其拆分。首先\(ans=zs_{i,j}\)(注意,这里的\(zs_{i,j}\)不再是\([i,j]\)块内的众数,而是其出现的次数),在考虑两边的散块中,数\(x\)的出现次数能否更新\(ans\)。

比如,现在正在考虑在左侧散块的下标为\(i\)的数\(x\)能否更新\(ans\)。\(p_i\)是\(i\)之前有多少个\(x\),那么至少要存在第\(ans+p_i\)个\(x\),且\(ans+p_i\)要在\([l,r]\)的数据范围以内,答案就可以被更新,ans++

若在右侧则同理。

Code

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<queue>
#include<vector>
#include<limits.h>
#define IL inline
#define re register
#define LL long long
#define ULL unsigned long long
#ifdef TH
#define debug printf("Now is %d\n",__LINE__);
#else
#define debug
#endif
using namespace std; template<class T>inline void read(T&x)
{
char ch=getchar();
int fu;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') fu=-1,ch=getchar();
x=ch-'0';ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
x*=fu;
}
inline int read()
{
int x=0,fu=1;
char ch=getchar();
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') fu=-1,ch=getchar();
x=ch-'0';ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*fu;
}
int G[55];
template<class T>inline void write(T x)
{
int g=0;
if(x<0) x=-x,putchar('-');
do{G[++g]=x%10;x/=10;}while(x);
for(int i=g;i>=1;--i)putchar('0'+G[i]);putchar('\n');
}
#define N 500010
#define S 800
int n,m,s,block,L[N],R[N],belong[N],zs[S][S],cnt[N],a[N],sum[N],pos[N];
vector<int>vec[N];
int lastans;
map<int,int>ys;
int main()
{
// freopen("5048.in","r",stdin);
// freopen("5048-hsh.out","w",stdout);
n=read();m=read();
s=sqrt(n);
block=n/s;
for(int i=1;i<=s;i++)
{
L[i]=n/s*(i-1)+1;
R[i]=n/s*i;
}
R[s]=n;
for(int i=1;i<=s;i++)
{
for(int j=L[i];j<=R[i];j++)
{
belong[j]=i;
}
}
for(int i=1;i<=n;i++)
{
ys[a[i]=read()]=0;
}
int rank=1;
for(map<int,int>::iterator it=ys.begin();it!=ys.end();it++)
{
it->second=rank;
rank++;
}
for(int i=1;i<=n;i++)
{
a[i]=ys[a[i]];
vec[a[i]].push_back(i);
pos[i]=vec[a[i]].size()-1;
}
// for(int i=1;i<=n;i++) cout<<a[i]<<endl;
for(int i=1;i<=s;i++)
{
memset(cnt,0,sizeof(cnt));
for(int j=i;j<=s;j++)
{
zs[i][j]=zs[i][j-1];
for(int k=L[j];k<=R[j];k++)
{
cnt[a[k]]++;
zs[i][j]=max(zs[i][j],cnt[a[k]]);
}
}
}
int ans=0;
for(int t=1,x,y;t<=m;t++)
{
x=read();
y=read();
x^=ans;
y^=ans;
if(x>y) swap(x,y);
ans=0;
if(belong[y]-belong[x]<=3)
{
for(int i=x;i<=y;i++) sum[a[i]]=0;
for(int i=x;i<=y;i++)
{
sum[a[i]]++;
ans=max(ans,sum[a[i]]);
}
}
else
{
ans=zs[belong[x]+1][belong[y]-1];
for(int i=x,now;i<=R[belong[x]];i++)
{
now=pos[i];
while(now+ans<vec[a[i]].size()&&vec[a[i]][now+ans]<=y) ans++;
}
for(int i=L[belong[y]],now;i<=y;i++)
{
now=pos[i];
while(now-ans>=0&&vec[a[i]][now-ans]>=x) ans++;
}
}
cout<<ans<<endl;
}
return 0;
}

小节

分块模板题的训练就到此完结了。

但是分块的玄学美妙优雅暴力之处不仅仅于此。

比如根号平衡等。

比如P5048 [Ynoi2019 模拟赛] Yuno loves sqrt technology III这道题其实还可以二分查找最后一个\(\le r\)的数,使得总时间复杂度变为\(O(NT+MN/T*\log t)\stackrel{T=\sqrt{N\log N}}{\longrightarrow}O(N\sqrt{N\log N})\)[1]级别的。

大概是因为这只是\(Ynoi\)的模拟赛吧,就没有那么卡时间。

但是遇到毒瘤题,根号平衡是一种很重要的卡常方法。

以后遇到了卡常分块的话可以记录一下。


  1. 算法竞赛进阶指南 0x44

洛谷 P5048 [Ynoi2019 模拟赛] Yuno loves sqrt technology III的更多相关文章

  1. [洛谷P5048][Ynoi2019模拟赛]Yuno loves sqrt technology III

    题目大意:有$n(n\leqslant5\times10^5)$个数,$m(m\leqslant5\times10^5)$个询问,每个询问问区间$[l,r]$中众数的出现次数 题解:分块,设块大小为$ ...

  2. 洛谷P5048 [Ynoi2019模拟赛]Yuno loves sqrt technology III(分块)

    传送门 众所周知lxl是个毒瘤,Ynoi道道都是神仙题 用蒲公英那个分块的方法做结果两天没卡过去→_→ 首先我们分块,预处理块与块之间的答案,然后每次询问的时候拆成整块和两边剩下的元素 整块的答案很简 ...

  3. 洛谷 P5048 - [Ynoi2019 模拟赛] Yuno loves sqrt technology III(分块)

    题面传送门 qwq 感觉跟很多年前做过的一道题思路差不多罢,结果我竟然没想起那道题?!!所以说我 wtcl/wq 首先将 \(a_i\) 离散化. 如果允许离线那显然一遍莫队就能解决,复杂度 \(n\ ...

  4. 洛谷 P5046 [Ynoi2019 模拟赛] Yuno loves sqrt technology I(分块+卡常)

    洛谷题面传送门 zszz,lxl 出的 DS 都是卡常题( 首先由于此题强制在线,因此考虑分块,我们那么待查询区间 \([l,r]\) 可以很自然地被分为三个部分: 左散块 中间的整块 右散块 那么这 ...

  5. Luogu P5048 [Ynoi2019模拟赛]Yuno loves sqrt technology III 分块

    这才是真正的$N\sqrt{N}$吧$qwq$ 记录每个数$vl$出现的位置$s[vl]$,和每个数$a[i]=vl$是第几个$vl$,记为$P[i]$,然后预处理出块$[i,j]$区间的答案$f[i ...

  6. P5048 [[Ynoi2019模拟赛]Yuno loves sqrt technology III]

    为什么我感觉这题难度虚高啊-- 区间众数的出现次数- 计算器算一下 \(\sqrt 500000 = 708\) 然后我们发现这题的突破口? 考虑分块出来[L,R]块的众数出现个数 用 \(\text ...

  7. [luogu5048] [Ynoi2019模拟赛] Yuno loves sqrt technology III

    题目链接 洛谷. Solution 思路同[BZOJ2724] [Violet 6]蒲公英,只不过由于lxl过于毒瘤,我们有一些更巧妙的操作. 首先还是预处理\(f[l][r]\)表示\(l\sim ...

  8. [Ynoi2019模拟赛]Yuno loves sqrt technology III

    题目大意: 给你一个长为n的序列a,m次询问,每次查询一个区间的众数的出现次数,强制在线. 解题思路: 出题人题解 众所周知lxl是个毒瘤,Ynoi道道都是神仙题 首先得离散化. 分块后,预处理Fi, ...

  9. [Luogu5048] [Ynoi2019模拟赛]Yuno loves sqrt technology III[分块]

    题意 长为 \(n\) 的序列,询问区间众数,强制在线. \(n\leq 5\times 10^5\). 分析 考虑分块,暴力统计出整块到整块之间的众数次数. 然后答案还可能出现在两边的两个独立的块中 ...

  10. [Ynoi2019模拟赛]Yuno loves sqrt technology I

    题目描述 给你一个长为n的排列,m次询问,每次查询一个区间的逆序对数,强制在线. 题解 MD不卡了..TMD一点都卡不动. 强制在线的话也没啥好一点的方法,只能分块预处理了. 对于每个块,我们设lef ...

随机推荐

  1. Windows编程----进程:命令行参数

    什么是进程的命令行参数 每个进程在启动(双击exe启动.cmd命令行启动或者由其他程序通过CreateProcess启动)的时候,都会有一个命令行参数给它.命令行的参数以空格区分.这个命令行总是不为空 ...

  2. Windows下Dll在Unity中使用的一般方式

    Windows下Dll在Unity中使用的一般方式 Unity中虽然已经有广泛的库和插件,但是相较于C++的库生态而言,还是有一定的差距:因此本篇博文记录Windows下将C++函数打包成动态链接库在 ...

  3. 【数据结构与算法】不同路径 III:使用哈密尔顿路径算法实现

    [数据结构与算法]不同路径 III:使用哈密尔顿路径算法实现 Java 不同路径 III https://leetcode-cn.com/problems/unique-paths-iii/ 解题思路 ...

  4. Python的日志

    Python的日志,看上去啰啰嗦嗦的.请大神写了个通俗易懂简单方便通用的日志: import logging # 配置日志记录级别和输出方式 logging.basicConfig(level=log ...

  5. Kubernetes基础教程-通过部署Nginx快速学习基本使用命令

    1.验证节点信息 1.1.查看控制平面组件的状态 [root@linux-servertwo software]# kubectl get cs Warning: v1 ComponentStatus ...

  6. kubernetes学习之CKA认证

    最近接触了很多kubernetes应用场景,学习了不少kubernetes的知识,所以想着官方认证一下CKA.不得不感叹:云时代真的来了. K8s的专业技术认证主要有以下几种: CKA(Kuberne ...

  7. 全网最详细的CM311-1A魔百和刷Armbian教程

    CM311-1A魔百和搭载了晶晨S905L3A芯片(实质上是S905X2的定制版本,两者在性能上并无显著差异).然而,遗憾的是,关于这款设备的网络教程相对较少,导致我在自学过程中遇到了不少挑战和障碍. ...

  8. tomcat非root用户启动

    部署远程服务器时候, 基本都是用root账户登录, 习惯上会直接使用root启动tomcat. 这样其实是有风险的, 黑客获取的权限即容器的权限, 如果容器运行权限就很高,被攻破黑客即可获取很高的权限 ...

  9. .NET 原生驾驭 AI 新基建实战系列(三):Chroma ── 轻松构建智能应用的向量数据库

    在人工智能AI和机器学习ML迅猛发展的今天,数据的存储和检索需求发生了巨大变化.传统的数据库擅长处理结构化数据,但在面对高维向量数据时往往力不从心.向量数据库作为一种新兴技术,专为AI应用设计,能够高 ...

  10. 康谋分享 | AD/ADAS的性能概览:在AD/ADAS的开发与验证中“大海捞针”!

    如果您希望从数百万小时的驾驶数据中查找特定的相关驾驶事件和未遂事故,以确保您的所需功能正确运行,最好的方法就是创建一个系统性能的概览分析,实现在数据日志中快速检索关注点.为此,康谋在本文将为您详细介绍 ...