【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 ; }