传送门:【CodeForces】455D Serega and Fun
题目分析:
看大神各种splay高效过之。。。实在无力啊。。。只会分块暴力搞了。。。
具体思路是这样的:
首先我们将整个数组用双向链表模拟,对于所有元素x,设L[x]为他实际上左边的元素,R[x]为他实际上右边的元素,然后将其分块,分块系数自己定。。设H[x]指向第x块开头的元素。
由于CF能开大数组的特性。。我们直接开块数*数组长度的二维大数组保存该块中某个元素的个数。
每次操作,我们先确定l和r所在的块,然后确定l和r在各自对应的块中相对于块中头元素的位移,然后暴力推出该位置对应的元素。设l位置上的元素为 ll ,r上的元素为 rr 。
对于操作一,如果l == r则直接不执行(我的可能会出一点问题,解决比较繁琐还不如直接特判)。设 ll 所在的块编号为pos_l, rr 所在的块编号为pos_r,将所有pos_l+1到pos_r的H[ i ]指向L[H[ i ]],即将 l 到 r 内所有的元素右移一位,也可以理解为将整个需要变动的块左移一位,然后执行删除元素 rr 的操作,再然后将其插入到 ll 的左边,并且如果 ll 正好是某个块的头元素,那么将该块的H[ ]指向rr。不要忘记修改元素在对应块中的计数!
对于操作二,暴力统计 ll 所在区间内k的个数以及 rr所在区间内k的个数(因为我们每个块保存的是整个块内k的个数,不能直接得到这个块内部分区间内k的个数),对于夹在中间的块,直接加上该块内k的个数即可。
分块题我觉得精髓在于调整分块系数。。。本题如果用n^0.5作为系数则能勉强过掉。。但是如果用n^0.618可以不到1000ms就搞定。。。
双向链表(双端队列)+分块,队友提了一下,很不错的想法!然后我们各自实现了一个,他是STL,我是自己模拟双向链表,STL跑的比直接模拟的要快,应该是STL里面有什么效率比较好的元素吧。。写了将近半天,能过掉真的很开心啊,不过话说一开始map直接TLE了。。。果然在CF平台上还是大数组靠谱。。
代码如下:
#include <map>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
#define REP( i , a , b ) for ( int i = a ; i < b ; ++ i )
#define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define REV( i , a , b ) for ( int i = a ; i >= b ; -- i )
#define CLR( a , x ) memset ( a , x , sizeof a )
const int SQR = 330 ;
const int MAXN = 100005 ;
int cnt[SQR][MAXN] ;
int L[MAXN] , R[MAXN] , H[SQR] ;
int a[MAXN] ;
int sqr ;
int last_ans ;
int n , m ;
void solve () {
int ch , l , r , k , x ;
last_ans = 0 ;
sqr = pow ( n , 0.618 ) ;
//CLR ( cnt , 0 ) ;
FOR ( i , 1 , n ) {
scanf ( "%d" , &a[i] ) ;
++ cnt[( i - 1 ) / sqr][a[i]] ;
L[i] = i - 1 ;
R[i] = i + 1 ;
}
L[1] = R[n] = -1 ;
FOR ( i , 0 , n / sqr ) H[i] = i * sqr + 1 ;
scanf ( "%d" , &m ) ;
REP ( _ , 0 , m ) {
scanf ( "%d%d%d" , &ch , &l , &r ) ;
l = ( l + last_ans - 1 ) % n + 1 ;
r = ( r + last_ans - 1 ) % n + 1 ;
if ( l > r ) swap ( l , r ) ;
if ( ch == 1 && l == r ) continue ;
int pos_l = ( l - 1 ) / sqr ;//l所在的块
int pos_r = ( r - 1 ) / sqr ;//r所在的块
int ll = H[pos_l] ;//l所在的块的头元素
int rr = H[pos_r] ;//r所在的块的头元素
l -= pos_l * sqr ;//l相对于所在块头元素的位移
r -= pos_r * sqr ;//r相对于所在块头元素的位移
REP ( i , 1 , l ) ll = R[ll] ;//找到l所在的位置对应的元素
REP ( i , 1 , r ) rr = R[rr] ;//找到r所在的位置对应的元素
if ( ch == 1 ) {
-- cnt[pos_r][a[rr]] ;//将元素rr从所在块删除
++ cnt[pos_l][a[rr]] ;//将元素rr添加到ll所在的块
if ( ll == H[pos_l] ) H[pos_l] = rr ;//如果ll正好是块头,那么该块的头部指针指向rr
REV ( i , pos_r , pos_l + 1 ) {
H[i] = L[H[i]] ;//将该块的左边块的最右端的元素添加到该块
-- cnt[i - 1][a[H[i]]] ;//将该块左端的最右端的元素从左端删除
++ cnt[i][a[H[i]]] ;//将该块左端的最右端的元素添加到该块的左端开头
}
//将rr从pos_r中删除
R[L[rr]] = R[rr] ;
L[R[rr]] = L[rr] ;
//将rr添加到ll的左边
L[rr] = L[ll] ;
R[rr] = ll ;
R[L[ll]] = rr ;
L[ll] = rr ;
/*
for ( int i = H[0] ; ~i ; i = R[i] )
printf ( "%d-" , a[i] ) ;
printf ( "\n" ) ;
for ( int i = 0 ; i <= sqr ; ++ i )
printf ( "H[%d] = %d : %d\n" , i , H[i] , a[H[i]] ) ;
*/
} else {
scanf ( "%d" , &k ) ;
k = ( k + last_ans - 1 ) % n + 1 ;
int ans = 0 ;
if ( pos_l == pos_r ) {
for ( int i = ll ; i != R[rr] ; i = R[i] ) if ( a[i] == k ) ans ++ ;
} else {
for ( int i = L[H[pos_l + 1]] ; i != L[ll] ; i = L[i] ) if ( a[i] == k ) ans ++ ;
for ( int i = H[pos_r] ; i != R[rr] ; i = R[i] ) if ( a[i] == k ) ans ++ ;
REP ( i , pos_l + 1 , pos_r ) ans += cnt[i][k] ;
}
printf ( "%d\n" , ans ) ;
last_ans = ans ;
}
}
}
int main () {
while ( ~scanf ( "%d" , &n ) )
solve () ;
return 0 ;
}