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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值