1. 程式人生 > >【CodeForces】455D Serega and Fun 雙向連結串列分塊暴力

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