题目传送门
题目改编背景
己亥年十月初一,气温8.2~22.9℃,天气晴朗,坐在墙边善于装弱的小F感到凉爽,想逗乐大家。于是他再度开始说“我这蒟蒻渣得就要退役了,你们都是巨神!”机房里的一部分人们被他的魅力所吸引,围到了他面前(半圆),他又开始装弱,可是又被大家看穿啦!围观群众齐声对小F说了小F刚才说的话,引发小F深度不满,很快他开始拿周围的物品攻击旁边的OIer。他的攻击对特定的人有等伤害的溅射效果。每位观众都有他考试的rank值,特定的人即为rank相同的所有人。作为狙神的小F每攻击到他视线内部的人,就会得到1单位的满意度,如果击中的人的rank值更小,他还会更满意(满意度加一个小于 2 − 64 2^{-64} 2−64 的正实数)。他让你计算,每次攻击应该打rank值为多少的人他才会更满意。
题目描述
围着小F的有
N
N
N 个人,他们在小F的思想里每个人的rank(可以并列的正整数)顺次可以看做数列
a
1
,
a
2
,
.
.
.
,
a
n
a_1,a_2,...,a_n
a1,a2,...,an 。他总共进行
M
M
M次攻击,每次询问一个区间
[
l
,
r
]
[l,r]
[l,r] 表示他的一次攻击的视野,请你回答出每次打rank为多少的人满意度最高。另外,小F不能让你离线。
实际上就是给定一个序列多次询问不同区间内的最小的众数
输入格式
第一行两个整数
n
,
m
n,m
n,m ,表示有
n
n
n 名观众,小F的
m
m
m 次攻击。
接下来一行n个空格分隔的整数
a
i
a_i
ai ,表示第
i
i
i 个人的rank值。
再接下来
m
m
m 行每行两个整数
l
0
,
r
0
l_0,r_0
l0,r0 ,我们令上次询问的结果为
x
x
x(如果这是第一次询问, 则
x
=
0
x=0
x=0 )。
令
l
=
(
l
0
+
x
−
1
)
m
o
d
n
+
1
,
r
=
(
r
0
+
x
−
1
)
m
o
d
n
+
1
l=(l_0+x-1)\bmod n+1,r=(r_0+x-1) \bmod n+1
l=(l0+x−1)modn+1,r=(r0+x−1)modn+1,如果
l
>
r
l>r
l>r,则交换
l
,
r
l,r
l,r 。
最终的询问区间为
[
l
,
r
]
[l,r]
[l,r] 。(样例见题)
输出格式
输出 m m m 行。每行一个正整数,表示每次询问的结果。(样例见题)
说明
对于100%数据, N ≤ 40 , 000 , M ≤ 50 , 000 , 1 ≤ a i ≤ 1 0 9 N \le 40,000,M \le 50,000,1 \le a_i \le 10^9 N≤40,000,M≤50,000,1≤ai≤109。
解析
这个题我几百天前就认识了。当时不会分块,只有离散化在装桶。时间复杂度 O ( N 2 ) O(N^2) O(N2) 只能得20分,开O2优化能得80,在加一些卡常数技巧可以得95。所以一定要做正解了。
暴力代码
#include<bits/stdc++.h>
using namespace std;
struct kk{
int x;
int y;
}b[66666];
int c[66666],tong[66666],a[66666];
inline bool Aprilfool(kk mm,kk nn)
{
return mm.x<nn.x;
}
inline int readd()
{
char ch=0;
int xx=0;
while(1)
{
ch=getchar();
if(ch>'9'||ch<'0')
{
return xx;
}
xx=10*xx+ch-48;
}
}
void writee(int xx)
{
if(xx>9)
{
writee(xx/10);
}
putchar(xx%10+48);
}
int main()
{
int n,m,abc=0;
n=readd(),m=readd();
for(int i=1;i<=n;++i)
{
b[i].x=c[i]=readd();
b[i].y=i;
}
sort(b+1,b+n+1,Aprilfool);
b[0].x=-1;
for(int i=1;i<=n;++i)
{
if(b[i].x>b[i-1].x)
{
++abc;
}
//cout<<b[i].y<<endl;
a[b[i].y]=abc;
}
/*for(int i=1;i<=n;i++)
{
cout<<a[i]<<" ";
}*/
int x=0;
++m;
while(--m)
{
memset(tong,0,sizeof(tong));
int l,r;
l=readd();
r=readd();
l=(l+x-1)%n+1;
r=(r+x-1)%n+1;
if(l>r)
{
int temp=l;
l=r;
r=temp;
}
/*for(int i=l;i<=r;i++)
{
printf("%d ",c[i]);
}*/
int maxx=-1,ans=0;
//cout<<" "<<l<<" "<<r<<endl;
for(register int i=l;i<=r;++i)
{
tong[a[i]]++;
//cout<<a[i]<<" ";
if(tong[a[i]]>maxx)
{
maxx=tong[a[i]];
ans=c[i];
}
if(tong[a[i]]==maxx)
{
if(c[i]<ans)
ans=c[i];
}
}
//cout<<endl;
writee(ans);
putchar('\n');
x=ans;
}
return 0;
}
毕竟是一道黑题,难度还是比较大的,分块不太好想,让我只能看题解了。数据有点大,可以用 O ( ( N + M ) N ) O((N+M) \sqrt N) O((N+M)N) 的优雅的暴力AC。
PART I
装桶因为数据太分散,所以我们就要先离散化。
for(int i=1;i<=n;++i)scanf("%d",&c[i]),a[i]=c[i];
sort(c+1,c+n+1);
int mm=unique(c+1,c+n+1)-c-1;maxx=mm;
for(int i=1;i<=maxx;i++)b[i]=c[i];
for(int i=1;i<=n;i++)a[i]=lower_bound(c+1,c+mm+1,a[i])-c;
PART II
建立块最好把块的左右端存下降低Debug难度
blo=sqrt(n);
for(int i=1;i<=blo;i++)L[i]=blo*(i-1)+1,R[i]=blo*i;
if(R[blo]<n)++blo,L[blo]=R[blo-1]+1,R[blo]=n;
for(int i=1;i<=blo;i++){
for(int j=L[i];j<=R[i];j++){
pos[j]=i;
}
}
PART III
由于没有修改,并且现场处理很麻烦,于是就预处理。
建立3个(2大)数组 ( p [ i ] [ j ] , q [ i ] [ j ] ) , s [ i ] [ j ] (p[i][j],q[i][j]),s[i][j] (p[i][j],q[i][j]),s[i][j]
p
p
p:块编号
[
i
,
j
]
[i,j]
[i,j] 全体最小的众数是多少。
做法:两个变量
i
,
j
i,j
i,j 枚举块号
O
(
(
N
)
2
)
O((\sqrt N)^2)
O((N)2),在
j
j
j 块内用桶统计每个数的出现次数(只需要管上一个最大值)
O
(
N
)
O(\sqrt N)
O(N),记得清零。所以总时间复杂度为
O
(
(
N
)
3
)
O((\sqrt N)^3)
O((N)3);
q
q
q:和
p
p
p 一起枚举,记下当前众数的最大出现次数。
s
s
s:
1
→
i
1 \to i
1→i 块中 离散过的
j
j
j 出现的次数。
做法:枚举每个块
O
(
N
)
O(\sqrt N)
O(N),内层1枚举所有元素(递推)
O
(
N
)
O(N)
O(N),再循环枚举对应块内所有元素的出现
O
(
N
)
O(\sqrt N)
O(N)。总时间复杂度为
O
(
N
N
)
O(N \sqrt N)
O(NN) 。
void prework(){
for(int i=1;i<=blo;i++){
memset(bkt,0,sizeof(bkt));
int tmp=0,tim=0;
for(int j=i;j<=blo;j++){
for(int k=L[j];k<=R[j];k++){
bkt[a[k]]++;
if(bkt[a[k]]>tim)tmp=a[k],tim=bkt[a[k]];
else if(bkt[a[k]]==tim)tmp=min(tmp,a[k]);
}
p[i][j]=tmp;
q[i][j]=tim;
}
}
for(int i=1;i<=blo;i++){
for(int j=1;j<=n;j++)s[i][a[j]]=s[i-1][a[j]];
for(int j=L[i];j<=R[i];j++)++s[i][a[j]];
}
}
PART IV
黄线(零散)部分:开桶暴力统计
O
(
N
)
O(\sqrt N)
O(N)
绿线(整块)部分:从以前已经预处理过的得出。
O
(
N
)
O(\sqrt N)
O(N)
最后拼在一起就完了
O
(
N
)
O(\sqrt N)
O(N)
共
M
M
M 次询问,所以时间复杂度为
O
(
M
N
)
O(M \sqrt N)
O(MN) 。
具体操作:
- 只有黄线:开一个桶,统计每个数出现的数即可。若遇到相等则取小的那个。
- 绿线+黄线:绿线:块号 [ i , j ] [i,j] [i,j] 直接由 p p p 数组直接得到,次数直接由 q q q 数组得到。众数 t t t 出现次数等于 s [ j ] [ t ] − s [ i ] [ t ] s[j][t] - s[i][t] s[j][t]−s[i][t] ;黄线:统计出现次数最多的最小黄线众数(黄线只有它才可能成为答案)。加在一起就是绿线众数加上它在黄线上的出现次数和当前黄线众数比较选出现更多(相同则更小的)即可得到答案。
int u=pos[l],v=pos[r],tmp=0;
if(v-u<2){
tmp=0;
for(int j=l;j<=r;j++)temp[a[j]]=0;
for(int j=l;j<=r;j++){
temp[a[j]]++;
if(temp[a[j]]>temp[tmp])tmp=a[j];
else if(temp[a[j]]==temp[tmp])tmp=min(tmp,a[j]);
}
}
else{
tmp=p[u+1][v-1];temp[tmp]=0;vis[tmp]=0;
for(int j=l;j<=R[u];j++)temp[a[j]]=0,vis[a[j]]=0;
for(int j=L[v];j<=r;j++)temp[a[j]]=0,vis[a[j]]=0;
for(int j=l;j<=R[u];j++)temp[a[j]]++;
for(int j=L[v];j<=r;j++)temp[a[j]]++;
int mxnum,mx=0;
for(int j=l;j<=R[u];j++){
if(!vis[a[j]]){
vis[a[j]]=1;
int val=temp[a[j]]+s[v-1][a[j]]-s[u][a[j]];
if(mx<val)mx=val,mxnum=a[j];
else if(mx==val)mxnum=min(mxnum,a[j]);
}
}
for(int j=L[v];j<=r;j++){
if(!vis[a[j]]){
vis[a[j]]=1;
int val=temp[a[j]]+s[v-1][a[j]]-s[u][a[j]];
if(mx<val)mx=val,mxnum=a[j];
else if(mx==val)mxnum=min(mxnum,a[j]);
}
}
if(mx>temp[tmp]+q[u+1][v-1])tmp=mxnum;
else if(mx==temp[tmp]+q[u+1][v-1])tmp=min(tmp,mxnum);
}
PART V
我们已经得到了离散化后的结果了,只需要转化为以前即可。
恭喜完成一道黑题,加油!
完整代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
using namespace std;
int a[66666],c[66666],b[66666];//离散化后、离散化前
int bkt[66666],maxx,n,m,temp[66666];//桶
int blo,pos[66666],L[333],R[333],vis[66666];
int s[333][66666],p[333][333],q[333][333];
void prework(){
for(int i=1;i<=blo;i++){
memset(bkt,0,sizeof(bkt));
int tmp=0,tim=0;
for(int j=i;j<=blo;j++){
for(int k=L[j];k<=R[j];k++){
bkt[a[k]]++;
if(bkt[a[k]]>tim)tmp=a[k],tim=bkt[a[k]];
else if(bkt[a[k]]==tim)tmp=min(tmp,a[k]);
}
p[i][j]=tmp;
q[i][j]=tim;
}
}
for(int i=1;i<=blo;i++){
for(int j=1;j<=n;j++)s[i][a[j]]=s[i-1][a[j]];
for(int j=L[i];j<=R[i];j++)++s[i][a[j]];
}
}
int solve(int l,int r){
int u=pos[l],v=pos[r],tmp=0;
if(v-u<2){
tmp=0;
for(int j=l;j<=r;j++)temp[a[j]]=0;
for(int j=l;j<=r;j++){
temp[a[j]]++;
if(temp[a[j]]>temp[tmp])tmp=a[j];
else if(temp[a[j]]==temp[tmp])tmp=min(tmp,a[j]);
}
}
else{
tmp=p[u+1][v-1];temp[tmp]=0;vis[tmp]=0;
for(int j=l;j<=R[u];j++)temp[a[j]]=0,vis[a[j]]=0;
for(int j=L[v];j<=r;j++)temp[a[j]]=0,vis[a[j]]=0;
for(int j=l;j<=R[u];j++)temp[a[j]]++;
for(int j=L[v];j<=r;j++)temp[a[j]]++;
int mxnum,mx=0;
for(int j=l;j<=R[u];j++){
if(!vis[a[j]]){
vis[a[j]]=1;
int val=temp[a[j]]+s[v-1][a[j]]-s[u][a[j]];
if(mx<val)mx=val,mxnum=a[j];
else if(mx==val)mxnum=min(mxnum,a[j]);
}
}
for(int j=L[v];j<=r;j++){
if(!vis[a[j]]){
vis[a[j]]=1;
int val=temp[a[j]]+s[v-1][a[j]]-s[u][a[j]];
if(mx<val)mx=val,mxnum=a[j];
else if(mx==val)mxnum=min(mxnum,a[j]);
}
}
if(mx>temp[tmp]+q[u+1][v-1])tmp=mxnum;
else if(mx==temp[tmp]+q[u+1][v-1])tmp=min(tmp,mxnum);
}
return tmp;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)scanf("%d",&c[i]),a[i]=c[i];
sort(c+1,c+n+1);
int mm=unique(c+1,c+n+1)-c-1;maxx=mm;
for(int i=1;i<=maxx;i++)b[i]=c[i];
for(int i=1;i<=n;i++)a[i]=lower_bound(c+1,c+mm+1,a[i])-c;
blo=sqrt(n);
for(int i=1;i<=blo;i++)L[i]=blo*(i-1)+1,R[i]=blo*i;
if(R[blo]<n)++blo,L[blo]=R[blo-1]+1,R[blo]=n;
for(int i=1;i<=blo;i++){
for(int j=L[i];j<=R[i];j++){
pos[j]=i;
}
}
prework();
int prev=0,ans=0;
for(int i=1;i<=m;i++){
int ll,rr;
scanf("%d%d",&ll,&rr);
ll=(ll+prev-1)%n+1;rr=(rr+prev-1)%n+1;
if(ll>rr)swap(ll,rr);
ans=solve(ll,rr);
printf("%d\n",c[ans]);
prev=c[ans];
}
return 0;
}