『蒲公英 区间众数 分块』
<更新提示>
<第一次更新>
<正文>
蒲公英
Description
在乡下的小路旁种着许多蒲公英,而我们的问题正是与这些蒲公英有关。
为了简化起见,我们把所有的蒲公英看成一个长度为n的序列(a1,a2,...,ai,...,an) ,其中 ai 为一个正整数,表示第i棵蒲公英的种类编号。
而每次询问一个区间[l,r],你需要回答区间里出现次数最多的是哪种蒲公英,如果有若干种蒲公英出现次数相同,则输出种类编号最小的那个。
注意,你的算法必须是在线的。
Input Format
第一行两个整数n,m,表示有n株蒲公英,m次询问。
接下来一行n 个空格分隔的整数ai ,表示蒲公英的种类。
再接下来m 行每行两个整数l0 , r0。我们令上次询问的结果为x(如果这是第一次询问,则x = 0)。
令l=(l0+x−1)mod n+1,r=(r0+x−1)mod n+1如果l>r,则交换l,r。
最终的查询区间为[l,r][l,r]。
Output Format
输出m 行。每行一个整数,表示每次询问的结果。
Sample Input
6 3
1 2 3 2 1 2
1 5
3 6
1 5
Sample Output
1
2
1
解析
这是一道经典的分块问题,由于众数不具有区间可加性,所以传统的线段树等数据结构全部都没用了。那么我们考虑使用分块算法,将序列\(a\)分为\(T\)块,每一块的长度为\(\frac{n}{T}\),则对于每一个询问\([l,r]\),我们设\(l\)所在块的右边界为\(L\),\(r\)所在块的左边界为\(R\),那么就将原序列分为三部分\([l,L],[L+1,R-1],[R,r]\),显然\([L+1,R-1]\)数若干个连续的块,那么区间\([l,r]\)在众数就一定来自于以下两种情况:
\(1.\) 区间\([L+1,R-1]\)的众数
\(2.\) 区间\([l,L],[R,r]\)中的某个数
那么我们考虑如下的算法:
我们预处理出以每一个段边界为端点的区间众数,并对每一个数值建立一个\(vector\),按顺序保存这个数值每一次出现的位置。
对于一个询问\([l,r]\),我们先直接选取\([L+1,R-1]\)的答案做为备选答案,然后依次扫描\([l,L],[R,r]\)中的每一个元素,利用二分查找在\(vector\)中找到这个数值在区间\([l,r]\)第一次和最后一次出现的位置,两个\(vector\)的下标相减即为这个数在区间\([l,r]\)中出现的次数,然后尝试更新答案。
此时,这个算法的时间复杂度为\(O(nT+nm/T*log_2n)\),取\(T=\sqrt{nlog_2n}\),则时间复杂度最小为\(O(n\sqrt{nlog_2n})\)。
\(Code:\)
#include <bits/stdc++.h>
using namespace std;
const int N=100020,SIZE=3020;
int n,t,raw[N],val[N],a[N];
int T,size,l[SIZE],r[SIZE],belo[N];
int cnt[N],mode[SIZE][SIZE];
vector < int > pos[N];
inline void input(void)
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d",&a[i]) , raw[++t] = a[i] ;
}
inline void setblocks(void)
{
T = sqrt( n * log2(n) ) , size = n/T;
for (int i=1;i<=T;i++)
{
if ( i*size > n ) break;
l[i] = (i-1) * size + 1;
r[i] = i * size;
}
if ( r[T] < n ) T++ , l[T] = r[T-1] + 1 , r[T] = n;
for (int i=1;i<=T;i++)
for (int j=l[i];j<=r[i];j++)
belo[j] = i;
}
inline void discrete(void)
{
sort( raw+1 , raw+t+1 );
t = unique( raw+1 , raw+t+1 ) - (raw+1);
for (int i=1;i<=n;i++)
val[i] = lower_bound( raw+1 , raw+t+1 , a[i] ) - raw;
}
inline void init(void)
{
for (int i=1;i<=n;i++)
pos[ val[i] ].push_back(i);
for (int i=1;i<=T;i++)
{
int Max = 0 , ans = 0;
memset( cnt , 0 , sizeof cnt );
for (int j=(i-1)*size+1;j<=n;j++)
{
cnt[ val[j] ]++;
if ( Max < cnt[val[j]] || ( Max==cnt[val[j]] && raw[val[j]] < raw[ans] ) )
Max = cnt[ val[j] ] , ans = val[j];
mode[i][belo[j]] = ans;
}
}
}
inline int query(int ql,int qr)
{
int L = belo[ql] , R = belo[qr];
if ( L + 1 > R - 1 )
{
int Max = 0 , ans = 0;
for (int i=ql;i<=qr;i++)
{
int tot = upper_bound( pos[val[i]].begin() , pos[val[i]].end() , qr )
- lower_bound( pos[val[i]].begin() , pos[val[i]].end() , ql );
if ( tot > Max || ( tot==Max && raw[val[i]] < raw[ans] ) )
Max = tot , ans = val[i];
}
return raw[ans];
}
int ans = mode[L+1][R-1];
int Max = upper_bound(pos[ans].begin(),pos[ans].end(),r[R-1]) - lower_bound(pos[ans].begin(),pos[ans].end(),l[L+1]);
for (int i=ql;i<=r[L];i++)
{
int tot = upper_bound( pos[val[i]].begin() , pos[val[i]].end() , qr )
- lower_bound( pos[val[i]].begin() , pos[val[i]].end() , ql );
if ( tot > Max || ( tot==Max && raw[val[i]] < raw[ans] ) )
Max = tot , ans = val[i];
}
for (int i=l[R];i<=qr;i++)
{
int tot = upper_bound( pos[val[i]].begin() , pos[val[i]].end() , qr )
- lower_bound( pos[val[i]].begin() , pos[val[i]].end() , ql );
if ( tot > Max || ( tot==Max && raw[val[i]] < raw[ans] ) )
Max = tot , ans = val[i];
}
return raw[ans];
}
inline void solve(void)
{
int ql,qr;
for (int i=1;i<=n;i++)
{
scanf("%d%d",&ql,&qr);
printf("%d\n",query(ql,qr));
}
}
int main(void)
{
input();
discrete();
setblocks();
init();
solve();
return 0;
}
<后记>