[Violet 6]蒲公英
题目
Description
亲爱的哥哥:
你在那个城市里面过得好吗?
我在家里面最近很开心呢。昨天晚上奶奶给我讲了那个叫「绝望」的大坏蛋的故事的说!它把人们的房子和田地搞坏,还有好多小朋友也被它杀掉了。我觉得把那么可怕的怪物召唤出来的那个坏蛋也很坏呢。不过奶奶说他是很难受的时候才做出这样的事的……
最近村子里长出了一大片一大片的蒲公英。一刮风,这些蒲公英就能飘到好远的地方了呢。我觉得要是它们能飘到那个城市里面,让哥哥看看就好了呢!
哥哥你要快点回来哦!
爱你的妹妹 Violet
Azure 读完这封信之后微笑了一下。
“蒲公英吗……”
在乡下的小路旁种着许多蒲公英,而我们的问题正是与这些蒲公英有关。
为了简化起见,我们把所有的蒲公英看成一个长度为 n 的序列 {a1,a2..an},其中 ai 为一个正整数,表示第 i 棵蒲公英的种类编号。
而每次询问一个区间 [l,r],你需要回答区间里出现次数最多的是哪种蒲公英,如果有若干种蒲公英出现次数相同,则输出种类编号最小的那个。
注意,你的算法必须是在线的。
Input
第一行有两个整数,分别表示蒲公英的数量 n 和询问次数 m。
第二行有 n 个整数,第 i 个整数表示第 i 棵蒲公英的种类 ai。
接下来 m 行,每行两个整数 l_0, r_0,表示一次询问。输入是加密的,解密方法如下:
令上次询问的结果为 x(如果这是第一次询问,则 x=0),设 l=((l_0+x−1) mod n)+1,r=((r_0+x−1) mod n)+1。如果 l>r,则交换 l,r。
最终的询问区间为计算后的 [l,r]。
Output
对于每次询问,输出一行一个整数表示答案。
Sample Input
6 3
1 2 3 2 1 2
1 5
3 6
1 5
Sample Output
1
2
1
Hint
数据规模与约定
- 对于 20% 的数据,保证 n,m≤3000。
- 对于 100% 的数据,保证 1≤n≤40000,1≤m≤50000,1≤ai≤10^9, 1≤l_0,r_0≤n。
思路
这是一道经典的区间求众数问题;
已知 区间$[x,y]$ 的众数和 区间$[y+1,z]$ 不能直接得到 区间$[x,z]$ 的众数;
所以用树状数组或线段树维护就非常困难;
所以我们可以使用分块做法;
首先我们可以预处理出每两个块之间的众数;
然后我们可以设一个 $vetor[i]$ 来保存 $i$ 这个数出现的位置;
ve[aa[i]].push_back(i);
记下数字 $aa[i]$ 出现的位置 $i$;
对于每个询问,在对应的 $vector$ 里二分查找即可得到 $x$ 在 $[l,r]$ 中出现的次数;
例如:
序列 $aa~=~{1,4,3,2,2,3,1,4,1,3,4} ~$
$vector[1]={1,7,9}$ , $1$ 出现的位置是 $1,7,9$ ;
$vector[2]={4,5}$ , $2$ 出现的位置是 $4,5$ ;
$vector[3]={3,6,10}$ ,$3$ 出现的位置是 $3,6,10$ ;
$vector[4]={2,8,11}$ ,$4$ 出现的位置是 $2,8,11$ ;
当我们访问区间 $[2,8]$ 的众数时;
我们可以在$vetor[x]$ 里找第一个 $ \geq~l$ 的数的下标,和第一个 $ \leq~r$ 的数的下标,然后把两下标相减加一即可;
inline ll findout(ll x,ll y,ll t)//找区间[x,y]中 ,t 出现的次数
{
ll sum=upper_bound(ve[t].begin(),ve[t].end(),y)
-lower_bound(ve[t].begin(),ve[t].end(),x);
return sum;
}
求$1$ 在 区间$[2,8]$里 出现的次数,$vector[1]$ 中第一个 $ \geq~l$ 的数组下标是 $2$,第一个 $ \leq~r$ 的数的下标是 $2$,所以 $1$ 在区间 $[2,8]$ 中出现了 $2-2+1=1$ 次;
求$2$ 在 区间$[2,8]$里 出现的次数,$vector[2]$ 中第一个 $ \geq~l$ 的数组下标是 $1$,第一个 $ \leq~r$ 的数的下标是 $2$,所以 $2$ 在区间 $[2,8]$ 中出现了 $2-1+1=2$ 次;
求$3$ 在 区间$[2,8]$里 出现的次数,$vector[3]$ 中第一个 $ \geq~l$ 的数组下标是 $1$,第一个 $ \leq~r$ 的数的下标是 $2$,所以 $3$ 在区间 $[2,8]$ 中出现了 $2-1+1=2$ 次;
求$4$ 在 区间$[2,8]$里 出现的次数,$vector[4]$ 中第一个 $ \geq~l$ 的数组下标是 $1$,第一个 $ \leq~r$ 的数的下标是 $2$,所以 $4$ 在区间 $[2,8]$ 中出现了 $2-1+1=2$ 次;
因为题目要求出现次数相同的情况,取编号最小的;
所以区间$[2,8]$ 的众数是
$2$ !!!
这样就好了,敲得我好累;
然后就只剩暴力了。。。。。。。。。。。
等等!!
还有要注意的是题目中的 $a[i]~\leq~10^9$ ,尼玛,太大了;
那么只能乖乖写离散化了,然后就还要记录每个数以前的标号;
代码
#include<bits/stdc++.h> using namespace std; typedef long long ll; inline ll read() { ll a=0,f=1; char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();} while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();} return a*f; }//为什么这里一片死寂 ll n,m,blo; ll a[50005],f[505][505]; ll num[50005],aa[50005],v[50005]; map<ll,ll>ff; vector<ll> ve[50005]; inline ll L(ll x) { return (x-1)*blo+1; }// x 分块的左端点 inline ll R(ll x) { return x*blo; }// x 分块的右端点 inline void beforework(ll x)//beforework 在我们做事之前 { memset(num,0,sizeof(num)); ll s=0,mx=0; for(ll i=L(x);i<=n;i++) { num[aa[i]]++;//暴力记录每个数出现的次数 if(num[aa[i]]>mx||num[aa[i]]==mx&&v[aa[i]]<v[s])//如果出现的次数多,或者出现次数一样但数的标号小 s=aa[i],mx=num[aa[i]];//保存下新的众数 f[x][a[i]]=s;//那么 s 就是 分块 x-a[i] 中新的众数 } } inline ll findout(ll x,ll y,ll t)//找区间[x,y]中 ,t 出现的次数 { //vetor[t] 里找第一个 >=l 的数的下标,和第一个 <=r 的数的下标,然后把两下标相减加一即可; ll sum=upper_bound(ve[t].begin(),ve[t].end(),y) -lower_bound(ve[t].begin(),ve[t].end(),x); return sum; } inline ll reallyans(ll x,ll y) { ll ans=f[a[x]+1][a[y]-1];//中间大分块已经处理过了,直接拿来用 ll mx=findout(x,y,ans);//但是需要再求一遍 众数 出现的 次数 ,方便统计新的众数 if(a[x]==a[y])//如果他们在一个分块里 { for(ll i=x;i<=y;i++) { ll tot=findout(x,y,aa[i]);//直接通过 二分暴力找出现次数 if(tot>mx||tot==mx&&v[aa[i]]<v[ans]) ans=aa[i],mx=tot; } } else//x y 不在同一分块 { //中间的大分块已经预处理过了,只要暴力小分块就好了 for(ll i=x;i<=R(a[x]);i++) { ll tot=findout(x,y,aa[i]);//直接通过 二分暴力找出现次数 if(tot>mx||tot==mx&&v[aa[i]]<v[ans])//比较看是否出现了新的众数 ans=aa[i],mx=tot; } for(ll i=L(a[y]);i<=y;i++) { ll tot=findout(x,y,aa[i]);//直接通过 二分暴力找出现次数 if(tot>mx||tot==mx&&v[aa[i]]<v[ans])//比较看是否出现了新的众数 ans=aa[i],mx=tot; } } return ans;//返回答案 } int main() { n=read();m=read();//读入 blo=sqrt(n);//分成 blo 个块 ll t=0; for(ll i=1;i<=n;i++) { aa[i]=read(); a[i]=(i-1)/blo+1;//记录每一个数属于那个块 if(!ff[aa[i]]) { ff[aa[i]]=++t;//离散化出新的标号 v[t]=aa[i];//记录以前的标号 } aa[i]=ff[aa[i]];//附上新的标号 ve[aa[i]].push_back(i);//记下每个数的出现位置 } for(ll i=1;i<=a[n];i++) beforework(i);//预处理出每两个分块的众数 ll ans=0; while(m--) { ll x=read(),y=read(); x=(x+ans-1)%n+1;y=(y+ans-1)%n+1;//解密 if(x>y) swap(x,y); ans=v[reallyans(x,y)];//find 答案 printf("%lld\n",ans);//输出 } return 0;//终于没忘打 return 0; }