Loading

寿司

不得不说这种题一点一点推还是很美妙的。

考场上我一直想先破环为链,然后\(n^2\)计算,但是奈何太菜,没推出来。
然后打了一个\(n!\)的式子和一个\(O(n)\)\(dp\)乱搞,最后想根据数据范围决定用哪个,结果手速没跟上,\(CE\)
思路有点问题,看到题面里面有个数据结构,总想那个单调栈,单调队列,链表啥的搞搞,没想到就是朴素的推一个式子。
我们首先需要发现,移动\(B\)和移动\(R\)是等效的,因为移动\(B\)的同时也在移动\(R\)
这里把\(R\)看作\(0\),\(B\)看作\(1\).
那我们选取一个移动的,本文以\(0\)为例。
我们需要在每个字母记录这个字母左右有多少个\(1\),也就是前缀和和后缀和。
\(ans=\sum_{i}^{n}min(pre_i,suf_i)\)
这个其实是最关键的,没有这个啥都没有。
怎么理解?为什么我考场上没有想出来?

  1. 对于每一个\(0\),我们只需要把它移到序列两边
    ——这是因为\(1\)\(0\)等效,把\(1\)移到两边 等价于 把\(0\)移到两边 等价于 把\(1\)移到中间
  2. 移动每一个\(0\)到两边,我们并不关心具体把哪个移到哪个上,我们之关心需要移多少步。这就不难发现:移到左边的步数是左边的\(1\),右边同理。

考场上一直卡在他具体要移动到哪个位置。

首先推出来式子之后就可以\(n^2\)解决,可以得到\(40pts\)

code
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <map>
#define mp make_pair
#define R register int
#define int long 
#define printf Ruusupuu = printf

int Ruusupuu ;

using namespace std ;
typedef long long L ;
typedef long double D ;
typedef unsigned long long G ;
typedef pair< int , int > PI ;
const int N = 2e6 + 10 ;

inline int read(){
	int w = 0 ; bool fg = 0 ; char ch = getchar() ;
	while( ch < '0' || ch > '9' ) fg |= ( ch == '-' ) , ch = getchar() ;
	while( ch >= '0' && ch <= '9' ) w = ( w << 1 ) + ( w << 3 ) + ( ch ^ '0' ) , ch = getchar() ;
	return fg ? -w : w ; 
}

inline void wap( int &a , int &b ){ a = a ^ b , b = a ^ b , a = a ^ b ; }
inline int mins( int a , int b ){ int zt = b - a ; return a + ( zt & ( zt >> 31 ) ) ; }
inline int maxs( int a , int b ){ int zt = b - a ; return b - ( zt & ( zt >> 31 ) ) ; }
inline int abss( int a ){ int zt = a >> 31 ; return ( a + zt ) ^ zt ; }

int n , m , T , ls [N] , l [N] , r [N] , top , ans = 0x3f3f3f3f , pos [N] ;
struct X{ int index , l , r ; } a [N] ;
char ch ; 

void sc(){
	T = read() ;
}

void work(){
	while( T -- ){
		n = 0 ; ans = 0x3f3f3f3f ;
		while( ch = getchar() ) {
			if( ch == 'B' ) ls [++ n] = 1 ;
			else if( ch == 'R' ) ls [++ n] = 0 ;
			else break ;
		}
		for( R i = 1 ; i <= n ; i ++ )
			ls [i + n] = ls [i] ;
		for( R i = 1 ; i <= 2 * n ; i ++ )
			l [i] = l [i - 1] + ( ls [i - 1] == 1 ) ;
		for( R i = 2 * n ; i >= 1 ; i -- )
			r [i] = r [i + 1] + ( ls [i + 1] == 1 ) ;
		
		for( R i = 1 ; i <= 2 * n ; i ++ ){
			if( ls [i] == 0 ) a [++ top].index = i , a [top].l = l [i] , a [top].r = r [i] ;
			pos [i] = top ;
		}
		
		for( R i = 1 ; i <= n ; i ++ ){
			int res = 0 ;
			for( R j = i ; j <= i + n ; j ++ )
				if( ls [j] == 0 ) res += min( l [j] - l [i] , r [j] - r [i + n] ) ;
			ans = min( ans , res ) ;
		}
	} 
}

signed main(){	
	sc() ;
	work() ;
	return 0 ;
} 

然后我们继续想,可以想到这个\(min\)取是有一定规律的。
在一个分界点前肯定有\(L_i>R_i\),在他后面肯定有\(R_i>=L_i\)
如果我们可以找到这个分界点,就可以用前缀和优化一一累加的过程。
不难发现这个分界点是单调的:因为一个序列中的\(B\)随下标增加不降。
这启示我们可以通过二分和倍增来实现分界点的寻找。
然而我尝试了两种倍增方法,常数有些大,二分恰好可以通过本题。
注意一个小问题,就是我们需要维护一个前缀和的前缀和,还需要通过加减一堆偏移量来实现\(O(1)\)查询一段区间内的\(L_i\)\(R_i\)的区间和。
我的做法有点非主流,因为我只在\(0\)上二分。
这个怎么说,不难推出来式子,但是很麻烦,需要耐下心来推。
然后就可以做到\(nlogn\)的复杂度了。
其实不难发现,这个分界点除了在一个序列内有单调性以外,在整个链顺时针移动的时候,分界点也不会逆时针移动。
因为我直接拿出来所有的\(0\),至少我们可以知道指针没有办法回到之前的链中。
其次,进行分类讨论:

如果把一个\(0\)移动,那么指针不降,因为少了一个\(l_i<r_i\)的点,多了一个\(l_i>r_i\)的点
如果把一个\(1\)移动,那么指针不降,因为所有的\(0\)\(l_i\)都减小\(1\)\(r_i\)都增加\(1\),只可能让\(l_i<r_i\)的点更多,点多之后指针自然会想后移动
故指针单调不降。

证毕。

这个时候需要用到和大佬类似的单调指针技巧
所以用一个\(for\)套一层\(while\)可以解决这个问题,单调指针移动过程均摊\(O(n)\)

具体实现看代码,如果你有耐心学习这种非主流做法。。(我敢赌没人学,,,)

code
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <map>
#define mp make_pair
#define R register int
#define int long 
#define printf Ruusupuu = printf

int Ruusupuu ;

using namespace std ;
typedef long long L ;
typedef long double D ;
typedef unsigned long long G ;
typedef pair< int , int > PI ;
const int N = 2e6 + 10 ;

inline int read(){
	int w = 0 ; bool fg = 0 ; char ch = getchar() ;
	while( ch < '0' || ch > '9' ) fg |= ( ch == '-' ) , ch = getchar() ;
	while( ch >= '0' && ch <= '9' ) w = ( w << 1 ) + ( w << 3 ) + ( ch ^ '0' ) , ch = getchar() ;
	return fg ? -w : w ; 
}

inline void wap( int &a , int &b ){ a = a ^ b , b = a ^ b , a = a ^ b ; }
inline int mins( int a , int b ){ int zt = b - a ; return a + ( zt & ( zt >> 31 ) ) ; }
inline int maxs( int a , int b ){ int zt = b - a ; return b - ( zt & ( zt >> 31 ) ) ; }
inline int abss( int a ){ int zt = a >> 31 ; return ( a + zt ) ^ zt ; }

int n , m , T , ls [N] , l [N] , r [N] , pos [N] , top ; 
int xl [N] , xr [N] , ix [N] ; L sl [N] , sr [N] , ans = 0x3f3f3f3f ;//为了卡常,不过要注意强制类型转换
char ch ; 

void sc(){
	T = read() ;
}

inline L tl( int inx , int ls ){ return xl [inx] - l [ls] ; }

inline L tr( int inx , int rs ){ return xr [inx] - r [rs + n - 1] ; }

inline int cal( int x ){ //第一种倍增方式,TLE80分
	int ll = pos [x] + ( ix [pos [x]] < x ) , rr = pos [n + x - 1] ;
	int gap = 1 ;
	while( ll <= rr && tl( ll , x ) <= tr( ll , x ) && gap ){
		int zt = gap + ll ;
		if( zt <= rr && tl( zt , x ) <= tr( zt , x ) ) ll = zt , gap *= 2 ;
		else gap /= 2 ; 
	} return ll ; 
}

inline int cla( int x ){//第二种倍增方式,TLE80分
	int ll = pos [x] + ( ix [pos [x]] < x ) , rr = pos [n + x - 1] ;
	for( R i = 20 ; i >= 0 ; i -- ){
		int zt = ll + ( 1 << i ) ; if( zt > top ) continue ;
		if( zt <= rr && tl( zt , x ) <= tr( zt , x ) ) ll = zt ;
	}  return ll ;
}

inline int tw( int x ){//二分,AC
	int ll = pos [x] + ( ix [pos [x]] < x ) , rr = pos [n + x - 1] ;
	while( ll < rr ){
		int zt = ( ll + rr ) >> 1 ;
		if( zt <= rr && tl( zt , x ) <= tr( zt , x ) ) ll = zt + 1 ;
		else rr = zt ;
	} return -- ll ;
}

void work(){
	while( T -- ){
		n = 0 ; ans = 0x7fffffffffff ; top = 0 ;
		while( ch = getchar() ) {
			if( ch == 'B' ) ls [++ n] = 1 ;
			else if( ch == 'R' ) ls [++ n] = 0 ;
			else break ;
		}// printf( "%ld\n" , n ) ;
		for( R i = 1 ; i <= n ; i ++ )
			ls [i + n] = ls [i] ;
		for( R i = 1 ; i <= 2 * n ; i ++ )
			l [i] = l [i - 1] + ( ls [i - 1] == 1 ) ; 
		for( R i = 2 * n ; i >= 1 ; i -- )
			r [i] = r [i + 1] + ( ls [i + 1] == 1 ) ;

		for( R i = 1 ; i <= 2 * n ; i ++ ){ 
			if( ls [i] == 0 ) xl [++ top] = l [i] , xr [top] = r [i] , ix [top] = i , sl [top] = sl [top - 1] + 1ll * xl [top] ;
			pos [i] = top ;
		} for( R i = top ; i >= 1 ; i -- ) sr [i] = sr [i + 1] + 1ll * xr [i] ; //维护前缀和的前缀和,注意强制类型转换
		
		int zt = 1 ;		
		for( R i = 1 ; i <= n ; i ++ ){
			// int cut = cal ( i ) ;//第一种倍增找分界点
			// int cut = cla ( i ) ;//第二种倍增找分界点
			// int cut = tw ( i ) ; //二分找分界点
			int ll = pos [i] + ( ix [pos [i]] < i ) , rr = pos [n + i - 1] ;	
			while( zt <= rr && tl( zt , i ) <= tr( zt , i ) ) zt ++ ;
			int cut = zt - 1 ;
			L res1 = ( sl [cut] - sl [ll - 1] ) - 1ll * ( cut - ll + 1 ) * (L) l [i] ;
			L res2 = ( sr [cut + 1] - sr [rr + 1] ) - 1ll * ( rr - cut ) * (L) r [i + n] ;
			ans = min( ans , res1 + res2 ) ;
		} printf( "%lld\n" , ans ) ;
	} 
}

signed main(){	
	sc() ;
	work() ;
	return 0 ;
} 

posted @ 2021-06-11 20:44  Soresen  阅读(41)  评论(0编辑  收藏  举报