传送门:【CodeForces】240F - TorCoder
题目大意:给你一个长度为n的字符串(下标从1~n)。现在给你m次操作,每次操作是一个区间【L,R】,如果这个区间内的字符串可以重排列回文串,那么这次操作就是将其变成回文串,如果可以构造多个,那么排列成字典序最小的。如果这次操作不能构成回文串,那么忽略它。最后你要输出字符串的最终形态。
题目分析:叉姐群有人提问的一道题,一开始看的时候什么思路都没有,更不要说想到线段树了(其实是没看题不知道题目长啥样,如果看到是m个询问估计一定会往线段树上面凑了),然后群里就有神说用线段树,仔细想了想,还真可以这么做。下面是我的思路。
首先构造一棵二维线段树,第一维是字母,第二维是区间的编号。那么我们应该维护什么?现在我们用1表示存在这个字符,0表示不存在。那么假设字符串的某个位置的字符为x,那么我们在x对应的线段树中的相应位置插入1,表示这个位置存在一个字符x。很显然一开始我们可以将字符挨个插到线段树中,可以得到这棵二维线段树的某n个叶子节点的值为1,其余位置为0。话说回来是不是听的云里雾里的。。。“插到线段树。。。那插过去干吗你倒是说明白啊。。。”好吧,那么既然我们插了那么多个1是为了什么?目的是:之后每个区间询问时我们可以快速得到这个区间某个字符的个数。既然我们需要知道个数,那么我们就维护区间和吧!于是乎我们要维护的东西就这么应要求粗线了。。。所以,我们要维护一个东西的时候我们一定要想明白我们需要什么,再决定要维护什么。
好了,既然知道我们要维护区间和了,那么我们需不需要lazy标记?跟着我的节奏往下看就知道了~
假设我们已经可以快速求出每个字符在相应区间的个数了,那么我们怎么判断可不可以构造成回文串?
很简单,看奇数个的字符有多少个,如果为0个,那么所有字符个数除以2左右对称就好了;如果有1个,那么就把奇数个的字符对半拆以后剩下的那一个放到中间去就好了;如果有2个或以上,那么一定构不成回文串了,不信你可以试试?
那么现在继续假设,假设我们可以构成回文串了,那么如果多种排列要字典序最小该怎么办?。。同样很简单。。。。。只要按照a-z的顺序挨个插入就好了,很显然这样得到的一定是字典序最小的。证明:假如某个排列是字典序最小的并且排列的前一半中,存在一个字符比在它前面的字符要小,那么交换一下这两个字符的位置必定可以得到一个字典序更小的排列,这与假设矛盾,得证。
好了,现在我们的新排列已经得到了,我们要更新字符串了,对原字符串直接更新?不用不用,直接更新线段树的相应信息就好了,最后用的时候再取出来就好了嘛~~~
那么怎么更新线段树?根据我们的排列易知,一种字符最多分成两部分,左右对称的。那么很显然插入的时候不是单个字符插入的了,那样效率太低,还是直接区间更新好了~区间更新再加上lazy标记处理,那么一次回文串询问的复杂度就是logN。
现在我们已经知道需要维护区间和以及成段更新需要lazy标记了,那么就可以尽情的玩耍了~~~
代码如下:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std ; #define ls ( o << 1 ) #define rs ( o << 1 | 1 ) #define lson ls , l , m #define rson rs , m + 1 , r #define rt o , l , r #define root 1 , 1 , n #define mid ( ( l + r ) >> 1 ) #define REP( i , n ) for ( int i = 0 ; i < n ; ++ i ) #define REV( i , n ) for ( int i = n ; i >= 0 ; -- i ) #define REPF( i , a , b ) for ( int i = a ; i <= b ; ++ i ) #define REPV( i , a , b ) for ( int i = a ; i >= b ; -- i ) #define clear( a , x ) memset ( a , x , sizeof a ) const int MAXN = 100005 ; int set[26][MAXN << 2] ; int sum[26][MAXN << 2] ; int cnt[26] ; char s[MAXN] ; void pushDown ( int x , int o , int l , int r ) { if ( ~set[x][o] ) { int m = mid ; set[x][ls] = set[x][rs] = set[x][o] ; sum[x][ls] = set[x][o] * ( m - l + 1 ) ; sum[x][rs] = set[x][o] * ( r - m ) ; set[x][o] = -1 ; } } void update ( int x , int c , int L , int R , int o , int l , int r ) { if ( L <= l && r <= R ) { set[x][o] = c ; sum[x][o] = c * ( r - l + 1 ) ; return ; } pushDown ( x , rt ) ; int m = mid ; if ( L <= m ) update ( x , c , L , R , lson ) ; if ( m < R ) update ( x , c , L , R , rson ) ; sum[x][o] = sum[x][ls] + sum[x][rs] ; } void query ( int L , int R , int o , int l , int r ) { if ( L <= l && r <= R ) { REP ( x , 26 ) cnt[x] += sum[x][o] ; return ; } REP ( x , 26 ) pushDown ( x , rt ) ; int m = mid ; if ( L <= m ) query ( L , R , lson ) ; if ( m < R ) query ( L , R , rson ) ; } void print ( int o , int l , int r ) { if ( l == r ) { REP ( x , 26 ) if ( sum[x][o] ) { printf ( "%c" , x + 'a' ) ; break ; } return ; } REP ( x , 26 ) pushDown ( x , rt ) ; int m = mid ; print ( lson ) ; print ( rson ) ; } void work () { int n , m ; int L , R ; freopen ( "input.txt" , "r" , stdin ) ; freopen ( "output.txt" , "w" , stdout ) ; while ( ~scanf ( "%d%d%s" , &n , &m , s + 1 ) ) { clear ( sum , 0 ) ; clear ( set , -1 ) ; REPF ( i , 1 , n ) update ( s[i] - 'a' , 1 , i , i , root ) ; REP ( i , m ) { scanf ( "%d%d" , &L , &R ) ; clear ( cnt , 0 ) ; query ( L , R , root ) ; int odd = 0 , idx ; REP ( i , 26 ) if ( cnt[i] % 2 ) { ++ odd ; idx = i ; } if ( odd > 1 ) continue ; REP ( i , 26 ) if ( cnt[i] ) update ( i , 0 , L , R , root ) ; if ( odd ) -- cnt[idx] ; REP ( i , 26 ) if ( cnt[i] ) { R = L + cnt[i] / 2 - 1 ; cnt[i] >>= 1 ; update ( i , 1 , L , R , root ) ; L = R + 1 ; } if ( odd ) { update ( idx , 1 , L , L , root ) ; ++ L ; } REV ( i , 25 ) if ( cnt[i] ) { R = L + cnt[i] - 1 ; update ( i , 1 , L , R , root ) ; L = R + 1 ; } //print ( root ) ; //putchar ( '\n' ) ; } print ( root ) ; putchar ( '\n' ) ; } } int main () { work () ; return 0 ; }