寿司
不得不说这种题一点一点推还是很美妙的。
考场上我一直想先破环为链,然后\(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)\)
这个其实是最关键的,没有这个啥都没有。
怎么理解?为什么我考场上没有想出来?
- 对于每一个\(0\),我们只需要把它移到序列两边
——这是因为\(1\)和\(0\)等效,把\(1\)移到两边 等价于 把\(0\)移到两边 等价于 把\(1\)移到中间 - 移动每一个\(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 ;
}