CH 4401/Luogu 4168 - 蒲公英 - [分块]
题目链接:传送门
题目链接:https://www.luogu.org/problemnew/show/P4168


题解:
经典的在线求区间众数的问题,由于区间众数不满足区间可加性,所以考虑分块,假设分块长度为 $S$,则总共分成 $T=N/S$ 块,
对于每个询问 $[l,r]$,设点 $l$ 在第 $p$ 块,点 $r$ 在第 $q$ 块,假设第 $p+1$ 到第 $q-1$ 块这整一个区间为 $[L,R]$,
那么,查询的区间就被分为 $[l,L)$ 和 $[L,R]$ 和 $(R,r]$ 三大块,显然可以分两种情况讨论:
1、$[L,R]$ 这个区间的众数就是 $[l,r]$ 的众数;
2、$[L,R]$ 这个区间的众数不是 $[l,r]$ 的众数,那么必然是由于 $[L,R]$ 区间内的某个数,它出现的次数,加上了 $[l,L)$ 和 $(R,r]$ 中出现的次数,超过了原本 $[L,R]$ 的众数;因此,这个必然在 $[l,L)$ 和 $(R,r]$ 中出现。
这样一来,就很好算了,不妨对于所有可行的 $[L,R]$,预处理出一个数组 $cnt_{L,R}$,记录区间 $[L,R]$ 内每个数字出现的次数,同时再记录 $[L,R]$ 的众数是哪个,
显然经过离散化处理后,$cnt_{L,R}$ 的空间复杂度为 $O(N)$,而所有可行的区间 $[L,R]$ 有 $O(T^2) = O(N^2 /S^2)$ 个;
那么,对于每次查询 $[l,r]$,$O(S)$ 枚举 $[l,L)$ 和 $(R,r]$ 中的出现的数,把它们加到 $[L,R]$ 对应的 $cnt_{L,R}$ 数组之中,维护最大值的同时与 $[L,R]$ 的众数的出现次数进行比较,就可以找到众数,
最后,再 $O(S)$ 地枚举 $[l,L)$ 和 $(R,r]$ 中的出现的数,把 $cnt_{L,R}$ 数组复原即可。
因此,对于每次查询,$O(S)$ 即可求得答案,总的就是 $O(MS)$;而预处理是 $O(NT^2) = O(N^3 /S^2)$;所以总时间复杂度为 $O(MS + N^3 /S^2)$,可知,当 $S = \sqrt[3]{{N^3 /M}} = \frac{N}{{\sqrt[3]{M}}}$ 时最小,为 $O(NM^{\frac{2}{3}})$。
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii; const int maxn=4e4+;
const int maxm=5e4+; int n,m;
int a[maxn],aid[maxn];
int cnt[][][maxn];
pii mode[][]; int block[maxn],len,tot;
int L[maxn],R[maxn]; vector<int> v;
inline int getid(int x){return lower_bound(v.begin(),v.end(),x)-v.begin()+;} void update(int x,int y,int i,pii &res)
{
cnt[x][y][aid[i]]++;
if(cnt[x][y][aid[i]]>res.first || (cnt[x][y][aid[i]]==res.first && a[i]<a[res.second]))
{
res.first=cnt[x][y][aid[i]];
res.second=i;
}
}
int ask(int l,int r)
{
int st=block[l],ed=block[r];
pii res;
if(ed-st<=)
{
res=make_pair(,);
for(int i=l;i<=r;i++) update(,,i,res);
for(int i=l;i<=r;i++) cnt[][][aid[i]]--;
}
else
{
res=mode[st+][ed-];
for(int i=l;i<=R[st];i++) update(st+,ed-,i,res);
for(int i=L[ed];i<=r;i++) update(st+,ed-,i,res);
for(int i=l;i<=R[st];i++) cnt[st+][ed-][aid[i]]--;
for(int i=L[ed];i<=r;i++) cnt[st+][ed-][aid[i]]--;
}
return res.second;
} int main()
{
cin>>n>>m;
len=max(,(int)(n/pow(m,1.0/3.0)));
for(int i=;i<=n;i++)
{
cin>>a[i];
v.push_back(a[i]); block[i]=(i-)/len+;
if(i==) L[block[i]]=i;
if(i==n) R[tot=block[i]]=i;
if(<=i && i<=n && block[i-]!=block[i])
{
R[block[i-]]=i-;
L[block[i]]=i;
}
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
for(int i=;i<=n;i++) aid[i]=getid(a[i]); memset(cnt,,sizeof(cnt));
for(int i=;i<=tot;i++)
{
for(int j=i;j<=tot;j++)
{
mode[i][j]=make_pair(,);
for(int k=L[i];k<=R[j];k++)
{
cnt[i][j][aid[k]]++;
if(cnt[i][j][aid[k]]>mode[i][j].first || (cnt[i][j][aid[k]]==mode[i][j].first && a[k]<a[mode[i][j].second]))
{
mode[i][j].first=cnt[i][j][aid[k]];
mode[i][j].second=k;
}
}
}
} int ans=;
while(m--)
{
int l,r;
scanf("%d%d",&l,&r);
l=(l+ans-)%n+;
r=(r+ans-)%n+;
if(l>r) swap(l,r);
printf("%d\n",ans=a[ask(l,r)]);
}
}
(被这题僵了好久……深深感到自己码力的弱小……)
题解2:
依然同上面一样,对于每个询问 $[l,r]$,设点 $l$ 在第 $p$ 块,点 $r$ 在第 $q$ 块,假设第 $p+1$ 到第 $q-1$ 块这整一个区间为 $[L,R]$,查询的区间就被分为 $[l,L)$ 和 $[L,R]$ 和 $(R,r]$ 三大块,
然后,这次不再建立数组 $cnt_{L,R}$,单纯记录区间 $[L,R]$ 的众数,
但是,我们与上面的大体思路是一样的,我们依然要暴力枚举 $[l,L)$ 和 $(R,r]$ 里所有出现的数,看看这些数在 $[l,r]$ 区间里出现的次数能不能超过区间 $[L,R]$ 的众数,
这样一来,我们考虑运用二分的方法:
首先用一个邻接表,其中每个链表存储的是表头数字在 $a[1:N]$ 的所有出现位置,
而后,我们对于 $[l,L)$ 和 $(R,r]$ 出现的任意一个数字,都能用两次二分查找到 $[l,r]$ 中的第一次出现位置和最后一次出现位置,两个位置相减即得 $[l,r]$ 内该数字出现了几次,
最后,就是同上面一样的,去和 $[L,R]$ 的区间众数比较,尝试更新区间众数。
那么,时间复杂度:预处理是 $O(NT) = O(N^2 / S)$;对于每次查询,$O(S \log N)$ 即可求得答案,总的就是 $O(\frac{{N^2 }}{S} + MS\log N)$,令 $S = \frac{N}{{\sqrt {M\log N} }}$,使其最小为 $O(N\sqrt {M\log N} )$。
AC代码2:
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii; const int maxn=4e4+;
const int maxm=5e4+; int n,m;
int a[maxn],aid[maxn];
vector<int> num[maxn];
int cnt[maxn];
pii mode[][]; int block[maxn],len,tot;
int L[maxn],R[maxn]; vector<int> v;
inline int getid(int x){return lower_bound(v.begin(),v.end(),x)-v.begin()+;} void update(int i,int l,int r,pii &res)
{
int x=lower_bound(num[aid[i]].begin(),num[aid[i]].end(),l)-num[aid[i]].begin();
int y=upper_bound(num[aid[i]].begin(),num[aid[i]].end(),r)-num[aid[i]].begin();
if(y-x>res.first || (y-x==res.first && a[i]<a[res.second]))
{
res.first=y-x;
res.second=i;
}
}
int ask(int l,int r)
{
int st=block[l],ed=block[r];
pii res;
if(ed-st<=)
{
res=make_pair(,);
for(int i=l;i<=r;i++) update(i,l,r,res);
}
else
{
res=mode[st+][ed-];
for(int i=l;i<=R[st];i++) update(i,l,r,res);
for(int i=L[ed];i<=r;i++) update(i,l,r,res);
}
return res.second;
} int main()
{
//freopen("input","r",stdin);
//freopen("my","w",stdout); cin>>n>>m;
len=max(,(int)(n/sqrt(m*log2(n))));
for(int i=;i<=n;i++)
{
cin>>a[i];
v.push_back(a[i]); block[i]=(i-)/len+;
if(i==) L[block[i]]=i;
if(i==n) R[tot=block[i]]=i;
if(<=i && i<=n && block[i-]!=block[i])
{
R[block[i-]]=i-;
L[block[i]]=i;
}
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
for(int i=;i<=n;i++)
{
aid[i]=getid(a[i]);
num[aid[i]].push_back(i);
} memset(mode,,sizeof(mode));
for(int i=;i<=tot;i++)
{
memset(cnt,,sizeof(cnt));
pii mx=make_pair(,);
for(int k=L[i],j=block[k];k<=n;k++,j=block[k])
{
cnt[aid[k]]++;
if(cnt[aid[k]]>mx.first || (cnt[aid[k]]==mx.first && a[k]<a[mx.second]))
{
mx.first=cnt[aid[k]];
mx.second=k;
}
if(mx.first>mode[i][j].first || (mx.first==mode[i][j].first && a[mx.second]<a[mode[i][j].second]))
mode[i][j]=mx;
}
} int ans=;
while(m--)
{
int l,r;
scanf("%d%d",&l,&r);
l=(l+ans-)%n+;
r=(r+ans-)%n+;
if(l>r) swap(l,r);
//printf("real l=%d r=%d\n",l,r);
printf("%d\n",ans=a[ask(l,r)]);
}
}
CH 4401/Luogu 4168 - 蒲公英 - [分块]的更多相关文章
- luogu P4168 蒲公英+ 分块学习笔记
		
传送门 题目描述 在乡下的小路旁种着许多蒲公英,而我们的问题正是与这些蒲公英有关. 为了简化起见,我们把所有的蒲公英看成一个长度为n的序列\((a_1,a_2..a_n)\),其中 \(a_i\)为一 ...
 - Luogu P4168 [Violet]蒲公英 分块
		
这道题算是好好写了.写了三种方法. 有一个好像是$qwq$$N\sqrt(N)$的方法,,但是恳请大佬们帮我看看为什么这么慢$qwq$(后面的第三种) 注:$pos[i]$表示$i$属于第$pos[i ...
 - [Violet]蒲公英   分块
		
发现写算法专题老是写不动,,,, 所以就先把我在luogu上的题解搬过来吧! 题目大意:查询区间众数,无修改,强制在线 乍一看是一道恐怖的题,仔细一看发现并没有那么难: 大致思路是这样的,首先我们要充 ...
 - BZOJ 2724: [Violet 6]蒲公英( 分块 )
		
虽然AC了但是时间惨不忍睹...不科学....怎么会那么慢呢... 无修改的区间众数..分块, 预处理出Mode[i][j]表示第i块到第j块的众数, sum[i][j]表示前i块j出现次数(前缀和, ...
 - 【BZOJ2724】[Violet 6]蒲公英 分块+二分
		
[BZOJ2724][Violet 6]蒲公英 Description Input 修正一下 l = (l_0 + x - 1) mod n + 1, r = (r_0 + x - 1) mod n ...
 - BZOJ 2724蒲公英 (分块) 【内有块大小证明】
		
题面 luogu传送门 分析 先分块,设块大小为x(之后我们会证明块大小取何值会更优) 步骤1 把所有的数离散化,然后对每个值开一个vector pos[i],pos[i]存储数i出现的位置 我们设查 ...
 - 洛谷P4168 蒲公英 分块处理区间众数模板
		
题面. 许久以前我还不怎么去机房的时候,一位大佬好像一直在做这道题,他称这道题目为"大分块". 其实这道题目的思想不只可以用于处理区间众数,还可以处理很多区间数值相关问题. 让我们 ...
 - BZOJ 2724: [Violet 6]蒲公英 [分块 区间众数]
		
传送门 题面太美不忍不放 分块分块 这种题的一个特点是只有查询,通常需要预处理:加入修改的话需要暴力重构预处理 预处理$f[i][j]$为第i块到第j块的众数,显然$f[i][j]=max{f[i][ ...
 - bzoj 2724 蒲公英 分块
		
分块,预处理出每两个块范围内的众数,然后在暴力枚举块外的进行比较 那么怎么知道每一个数出现的次数呢?离散后,对于每一个数,维护一个动态数组就好了 #include<cstdio> #inc ...
 
随机推荐
- Android——SeekBar(拖动条)相关知识总结贴
			
Android进度条(ProgressBar)拖动条(SeekBar)星级滑块(RatingBar)的例子 http://www.apkbus.com/android-51326-1-1.html A ...
 - Swift Assert 断言
			
前言 对每次运行都会出现的错误通常不会过于苦恼,可以使用断点调试或者 try catch 之类的方式判断并修复它.但是一些偶发(甚至是无数次运行才会出现一次)的错误单靠断点之类的方式是很难排除掉的,为 ...
 - Benchmark Web App 性能瓶颈分析与性能测试工具的使用方法总结
			
主要分为以下几个要素的指标: Disk IO . CPU . mem . Net . MySQL Web性能测试工具: 客户端 服务器端: 服务器性能测试工具: 服务器性能瓶颈分析工具: ab, si ...
 - c#实现windows远程桌面连接程序代码
			
使用winform制作windows远程桌面连接程序,windows自带了远程桌面连接,我们需要将远程桌面连接集成 到自己的winform程序,并实现管理远程主机的配置. 远程桌面核心类库 windo ...
 - “RESOURCE MONITOR“CPU占用特别高
			
背景: SQL Server 2008 R2 10.50.1600 没有设置页面文件,内存为64G,数据库分配50G cpu使用占了50%以上,平时只有10-20%,某台服务器“RESOURCE MO ...
 - ThinkPad T420 Fn+F5
			
关于F5,可做如下设置: 1)官网win7系统下载SIhotkey[8jvu39ww].exe:最新版本的我没测试,应该也可以用. 2)双击安装,并按程序安装,直到要你选择安装on s ...
 - 奇怪的Excel导入问题,OleDbException (0x80004005): 未指定的错误
			
windows server 2008 的安全性做的比较好,首先进入系统盘(一般都是C:\),点击工具--文件夹选项--查看,把“使用共享向导(推荐)”的勾去掉,把“隐藏受保护的操作系统文件(推荐)” ...
 - Go Revel - Jobs(任务调度模块)
			
revel提供了一个框架用于脱离请求流程的执行异步任务,一般用来执行经常运行的任务.更新缓存数据或发送邮件等. ##启用 该框架作为一个可选的revel模块,默认并不启用.需要更改应用配置来启用它: ...
 - Git 标签操作
			
允许有意义的名称到一个特定的版本库中的标签操作.Tom 决定标记他们的项目代码,以便他们以后可以更容易访问. 创建标签 让我们标记当前HEAD使用git tag命令.他提供的标记名称前加上-a选项,使 ...
 - 关于指针*前后const代表的意思——记住一个口诀“左内右本”
			
记住一个口诀“左内右本”:const出现在*的左边,则说明指针所指向的内容是常量const出现在*的右边,则说明指针本身是常量如果*左右两边都出现const,那么说明指针本事是常量,它所指向的内容也是 ...