[JSOI2008]火星人 题解
这道题,让我对文艺平衡树有了一些了解(话说要是只打那个板子我啥也没懂)
\(\mathtt{prework}\)
前言:如果您有耐心看我的做法,我将非常荣幸,如果您打算实现,我祝您体会到调试的快乐。(借用沈队的话)
所以说,先放出来那个啥也没让我懂的板子
文艺平衡树
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <iostream>
#define mp make_pair
#define R register int
#define int long
#define printf Ruusupuu = printf
#define scanf Ruusupuu = scanf
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 ;
const int Inf = 0x3f3f3f3f ;
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 ;
}
int root , znt , lz [N] , fa [N] , ch [N][2] , size [N] , son [N] , val [N] ;
#define zt( x ) son [x] = ( son [ch [x][0]] + son [ch [x][1]] + size [x] )
#define sk( x ) ( x == ch [fa [x]][1] )
#define tr( x , dlt ) ch [x][dlt > val [x]]
inline void sp( int x ){
if( !lz [x] ) return ;
lz [ch [x][0]] ^= 1 , lz [ch [x][1]] ^= 1 ;
lz [x] = 0 , swap( ch [x][0] , ch [x][1] ) ;
}
inline void spinup( int x ){
R y = fa [x] , z = fa [y] , k = sk( x ) ;
ch [z][sk(y)] = x , fa [x] = z ;
ch [y][k] = ch [x][k ^ 1] , fa [ch [x][k ^ 1]] = y ;
ch [x][k ^ 1] = y , fa [y] = x ;
zt( y ) , zt( x ) ;
}
inline void splay( int x , int pos ){
while( fa [x] != pos ){
R y = fa [x] ; R z = fa [y] ;
if( z != pos ) sk( x ) ^ sk ( y ) ? spinup( x ) : spinup( y ) ; //on the same line ? spinup( y ) : spinup( x ) ;
spinup( x ) ;
} if( !pos ) root = x ;
}
inline void ins( int dlt ){
R now = root , fh = 0 ;
while( now && val [now] != dlt ) fh = now , now = tr( now , dlt ) ;
if( now ) size [now] ++ ;
else {
now = ++ znt ;
if( fh ) tr( fh , dlt ) = now ;
size [now] = son [now] = 1 ;
fa [now] = fh , val [now] = dlt ;
} splay( now , 0 ) ;
}
inline int rk( int rank ){
R now = root ;
while( 1 ){
sp( now ) ; R nxt = ch [now][0] ;
if( size [now] + son [nxt] < rank ) rank -= ( size [now] + son [nxt] ) , now = ch [now][1] ;
else if( son [nxt] >= rank ) now = nxt ;
else return now ;
}
}
int n , m , l , r ;
void out( int now ){
sp( now ) ;
if( ch [now][0] ) out( ch [now][0] ) ;
if( val [now] > 1 && val [now] < n + 2 ) printf( "%ld " , val [now] - 1 ) ;
if( ch [now][1] ) out( ch [now][1] ) ;
}
inline void addtag( int l , int r ){
l = rk( l ) , r = rk( r + 2 ) ;
splay( l , 0 ) , splay( r , l ) ;
lz [ch [ch [root][1]][0]] ^= 1 ;
}
void sc(){
n = read() , m = read() ;
}
void work(){
for( R i = 1 ; i <= n + 2 ; i ++ ) ins( i ) ;
while( m -- ){
l = read() , r = read() ;
addtag( l , r ) ;
} out( root ) , puts( "" ) ;
}
signed main(){
sc() ;
work() ;
return 0 ;
}
在文艺平衡树中,靠一个操作实现了区间反转—打标记。
先通两次旋转提取出区间\([l,r]\)。
但是平衡树中并咩有代表区间的节点,所以,一个子树代表一个区间。
一个子树代表一个区间,是\(splay\)灵活处理区间问题的关键。
那也就是,我们可以把子树的根看作这个区间的代表。
一个点要想脱离这个根的束缚,反手当他的爸爸,那么必须先把标记吃了,这样就可以保证所有标记都会生效。
同理,通过提取区间->修改区间 或者 提取区间->查询区间 的操作,可以让 \(splay\) 灵活的处理区间问题。
当然,和线段树一样,在修改,查询之前,需要 \(spread\) , 修改之后需要 \(update\)。
\(\mathtt{mysolve}\)
由于做这道题的时候,我对\(splay\)的了解还不是很深,所以用了一个很难实现的做法,思路难度中规中矩。
还是说一下这个沙雕思路吧,我觉得可能还是有点用的。
\(\mathtt Q\) 查询
假如有一个字符串\(\mathtt{abcbc}\),我们要查询两个后缀的最长公共前缀,假设为\(\mathit{2,4}\)。
如果我们可以把每个后缀都当成一个字符串,并得到他的\(hash\)值,那么我们就可以通过二分长度得到答案。
具体过程是这样的
- 选定一个长度\(mid\)
- 得到\(\mathit{x,x+mid,y,y+mid}\) 的\(hash\)
- 用\(hash\)值做差,通过移位比较是否相等。
如果我们从最前面开始\(hash\)的话,整个操作将不太好实现,尤其还有后面的操作就更不好实现了。
所以,我从后面开始\(hash\)。
这样,\(\mathtt{abcbc}\)的各后缀的\(hash\)就是(从前往后)
\(01212,1212,212,12,2\) (这里为了放便采用三进制而不是\(131/13331\)进制)
在比较\(2\)和\(4\)的时候,假设当前\(mid\)是\(2\),那么我们用\(4-5,2-3\)就得到了两个要比较的串的\(Hash\)值。
分别是\(10,1000\),因为\(hash\)取模数了,所以除法貌似并不太好弄,所以我们把\(10\)左移\(4-2\)位(\(4,2\)是输入的查询值),就可以判断这两个字符串相等。
现在,只要可以维护插入和修改就可以了。
\(\mathtt R\) 修改
对于修改,我们可以计算出该后的字符串和改前的字符串的差值,然后给\(1-x\)的所有值都加上这个差值。
还是上面那个例子,我需要把\(\mathtt{abcbc}\) 改成 \(\mathtt{abdbc}\)
那么,该完后的后缀\(hash\)是\(01312,1312,312,12,2\)
也就是\(1-3\)的都加上了\(00100\),这个直接弄一个加法标记就可以了。
\(\mathtt I\) 插入
这个是最恶心的。
如果我们要在原字符串的\(2\)后面插入一个\(d\),那么插完后的字符串将变成\(\mathtt{abdcbc}\)
各后缀将变成\(013212,13212,3212,212,12,2\)
也就是说,我们需要把\(1-x\)的\(hash\)值前\(x\)位先左移,在执行一个\(\mathtt R\)就行了。
拿单点举例子,实现的时候通过打标记搞成区间就行了。
\(Aim \ \ \ 01212->013212\)
- 让原数减去\(x\)后面的值,设为\(x\), \(01212->01000\)
- 乘上禁止数,即左移一位, \(01000->010000\)
- 在加上\(x\),\(01000->010212\)
- 执行一个\(\mathtt R\),把\(0\)改成\(3\), \(010212->013212\)
这样的话,我们就需要乘法标记和加法标记共存,但不慌,仔细撕烤,可以发现标记应该这样做
打标记
假设原数是\(x\),两个标记上去是\(kx+t\),现在要变成\(z(kx+t)+r\)
很明显,展开柿子就行了,即给乘法标记,加法标记先乘上需要乘的数,在给加法标记加上需要加的数。
传标记
就是给儿子的标记和值域执行打标记的操作,没什么区别
然后,这就快结束了,但是还有一个问题
\(\Large \text {新插入的节点的关键值是什么?}\)
为了解决这个问题,我们需要先把\(x+1-n\)的节点的关键值加\(1\),然后在插入这个点。
所以,一共三个标记,码量巨大,细节也很多。
\(\huge 当需要读入一个字符或者字符串的时候\)
\(\huge 我们都直接搞一个字符串然后取他的第一个字符\)
这样可以忽略空格,否则交上去会出现很多玄学错误
code
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <iostream>
#include <assert.h>
#define mp make_pair
#define R register int
#define int long
#define scanf Ruusupuu = scanf
#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 = 5e5 + 10 ;
const int Inf = 0x3f3f3f3f ;
const int M = 1e9 + 7 ;
const int P = 131 ;
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 ;
}
char s [N] , kk [30] , bt [30] ; int m , Hs [N] , x , y , base [N] , len ;
int root , znt , fa [N] , size [N] , son [N] , ch [N][2] , has [N] , lzadd [N] , lzmul [N] , lzval [N] , val [N] ;
#define zt( x ) son [x] = ( son [ch [x][0]] + son [ch [x][1]] + size [x] )
#define sk( x ) ( x == ch [fa [x]][1] )
#define tr( x , dlt ) ch [x][dlt > val [x]]
inline void sp( int x ){
if( !lzadd [x] && lzmul [x] == 1 ) return ;
int k = lzadd [x] , t = lzmul [x] ; lzadd [x] = 0 , lzmul [x] = 1 ;
if( ch [x][0] ){
lzadd [ch [x][0]] = ( 1ll * lzadd [ch [x][0]] * t + k ) % M ;
lzmul [ch [x][0]] = ( 1ll * lzmul [ch [x][0]] * t ) % M ;
has [ch [x][0]] = ( 1ll * has [ch [x][0]] * t + k ) % M ;
}
if( ch [x][1] ){
lzadd [ch [x][1]] = ( 1ll * lzadd [ch [x][1]] * t + k ) % M ;
lzmul [ch [x][1]] = ( 1ll * lzmul [ch [x][1]] * t ) % M ;
has [ch [x][1]] = ( 1ll * has [ch [x][1]] * t + k ) % M ;
}
}
inline void spval( int x ){
if( !lzval [x] ) return ;
int k = lzval [x] ; lzval [x] = 0 ;
if( ch [x][0] ) lzval [ch [x][0]] += k , val [ch [x][0]] += k ;
if( ch [x][1] ) lzval [ch [x][1]] += k , val [ch [x][1]] += k ;
}
inline void spinup( int x ){
R y = fa [x] , z = fa [y] , k = sk ( x ) ;
ch [z][sk(y)] = x , fa [x] = z ;
ch [y][k] = ch [x][k ^ 1] , fa [ch [x][k ^ 1]] = y ;
ch [x][k ^ 1] = y , fa [y] = x ;
zt( y ) , zt( x ) ;
}
inline void splay( int x , int pos ){
while( fa [x] != pos ){
R y = fa [x] , z = fa [y] ;
if( z != pos ) sk( x ) ^ sk( y ) ? spinup( x ) : spinup( y ) ;
spinup( x ) ;
} if( !pos ) root = x ;
}
inline void ins( int dlt , int hs ){
R now = root , fh = 0 ;
while( now && val [now] != dlt ) sp( now ) , spval( now ) , fh = now , now = tr( now , dlt ) ;
if( now ) size [now] ++ ;
else{
now = ++ znt ;
fa [now] = fh , val [now] = dlt , has [now] = hs ;
son [now] = size [now] = lzmul [now] = 1 ;
if( fh ) tr( fh , dlt ) = now ;
} splay( now , 0 ) ;
}
inline int rk( int rank ){
R now = root ;
if( son [now] < rank ) return 0 ;
while( 1 ){
sp( now ) , spval( now ) ; R nxt = ch [now][0] ;
if( son [nxt] + size [now] < rank ) rank -= ( son [nxt] + size [now] ) , now = ch [now][1] ;
else if( son [nxt] >= rank ) now = nxt ;
else { splay( now , 0 ) ; return now ; }
}
}
void sc(){
ios::sync_with_stdio( false ) ;
scanf( "%s" , s + 1 ) , m = read() ;
base [0] = 1 ; for( R i = 1 ; i < N ; i ++ ) base [i] = ( 1ll * base [i - 1] * P ) % M ;
}
inline bool check( int s , int bx , int by ){
int idx = rk( x + s + 1 ) , idy = rk( y + s + 1 ) ;
int hsx = ( ( has [idx] - has [bx] ) % M + M ) % M , hsy = ( ( has [idy] - has [by] ) % M + M ) % M ;
if( val [bx] > val [by] ) return ( ( 1ll * base [val [bx] - val [by]] * hsx ) % M == hsy ) ;
else return ( ( 1ll * base [val [by] - val [bx]] * hsy ) % M == hsx ) ;
}
inline void lcqwq( int x , int y ){
int idx = rk( x + 1 ) , idy = rk( y + 1 ) ;
int lside = 1 , rside = son [root] - 2 , ans = 0 ;
while( lside <= rside ){
int mid = ( lside + rside ) >> 1 ;
if( mid + max( x , y ) > son [root] - 1 ){ rside = mid - 1 ; continue ; }
if( check( mid , idx , idy ) ) ans = mid , lside = mid + 1 ;
else rside = mid - 1 ;
} printf( "%ld\n" , ans ) ;
}
inline void Cge( int x ){
int idx = rk( x + 1 ) , idp = rk( x + 2 ) ;
int sto = ( 0ll + has [idp] + 1ll * base [len - x] * ( bt [0] - 'a' ) ) % M , orz = ( ( sto - has [idx] ) % M + M ) % M ;
int l = rk( 1 ) , r = idp ;
splay( l , 0 ) , splay( r , l ) ;
lzadd [ch [ch [root][1]][0]] = ( lzadd [ch [ch [root][1]][0]] + orz ) % M ;
has [ch [ch [root][1]][0]] = ( has [ch [ch [root][1]][0]] + orz ) % M ;
}
inline void Ins( int x ){
int idx = rk( x + 1 ) , idp = rk( x + 2 ) , valthis = val [idp] >= Inf ? len + 1 : val [idp] ;
int addtag = ( ( 1ll * base [len - x] * ( bt [0] - 'a' ) - 130ll * has [idp] ) % M + M ) % M ;
int sto = ( 1ll * base [len - x] * ( bt [0] - 'a' ) + has [idp] ) % M ;
int l = rk( 1 ) , r = idp ;
splay( l , 0 ) , splay( r , l ) ;
has [ch [ch [root][1]][0]] = ( 131ll * has [ch[ch [root][1]][0]] + addtag ) % M ;
lzmul [ch [ch [root][1]][0]] = ( 131ll * lzmul [ch [ch [root][1]][0]] ) % M ;
lzadd [ch [ch [root][1]][0]] = ( 131ll * lzadd [ch [ch [root][1]][0]] ) % M ;
lzadd [ch [ch [root][1]][0]] = ( 0ll + lzadd [ch [ch [root][1]][0]] + addtag ) % M ;
l = rk( x + 1 ) , r = rk( len + 2 ) ;
splay( l , 0 ) , splay( r , l ) ;
if( ch [ch [root][1]][0] ) val [ch [ch [root][1]][0]] += 1 , lzval [ch [ch [root][1]][0]] += 1 ;
ins( valthis , sto ) , len ++ ;
}
void work(){
ins( -Inf , 0 ) , len = strlen( s + 1 ) ;
for( R i = len ; i >= 1 ; i -- ) Hs [i] = ( 0ll + Hs [i + 1] + 1ll * base [len - i] * ( 0ll + s [i] - 'a' ) ) % M ;
for( R i = 1 ; i <= len ; i ++ ) ins( i , Hs [i] ) ;
ins( Inf , 0 ) ;
while( m -- ){
scanf( "%s", kk ) ;
if( kk [0] == 'Q' ) x = read() , y = read() , lcqwq( x , y ) ;
if( kk [0] == 'R' ) x = read() , scanf( "%s" , bt ) , Cge( x ) ;
if( kk [0] == 'I' ) x = read() , scanf( "%s" , bt ) , Ins( x ) ;
}
}
signed main(){
sc() ;
work() ;
return 0 ;
}
\(\mathtt{stdsolve}\)
正解很完美的利用了\(splay\),应该对文艺平衡树理解了的就可以写出来。
我是看完题解才感觉有点理解文艺平衡树。
和文艺平衡树一样,每个节点保存以这个节点为根的子树的\(hash\)值。
这才是平衡树的精髓,动态维护区间信息
我可能只是把平衡树当成了可以插入的线段树,才会导致我想出,写出那种做法。
维护这个信息只需要在上传信息的函数里面加点东西就可以了。
\(hash\)值的维护方法也比之前的简单多了
然后直接比较就行了。
对于修改,把它\(splay\)到根,然后直接改,\(upd\)一下就行。
对于插入,也不必给他开一个编号,因为这个操作里面,我们并不需要查找一个\(rank\)对应的值。
我们只需要生硬的把那个点插在那个地方,然后不管如何改变形态,他的大小关系都是不会变的
其实这个操作主要是对\(splay\) 的了解程度,实现难度和思路难度都不算太大。
code
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <iostream>
#include <assert.h>
#define mp make_pair
#define R register int
#define int long
#define scanf Ruusupuu = scanf
#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 = 5e5 + 10 ;
const int Inf = 0x3f3f3f3f ;
const int M = 1e9 + 7 ;
const int P = 131 ;
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 ;
}
char s [N] , kk [30] , bt [30] ; int m , Hs [N] , x , y , base [N] , len ;
int root , znt , fa [N] , size [N] , son [N] , ch [N][2] , has [N] , val [N] , lav [N] , tson [N] ;
#define sk( x ) ( x == ch [fa [x]][1] )
#define tr( x , dlt ) ch [x][dlt > val [x]]
inline void zt( int x ){
son [x] = son [ch [x][0]] + son [ch [x][1]] + size [x] ;
tson [x] = tson [ch [x][0]] + tson [ch [x][1]] + ( x > 2 ) ;
has [x] = ( 1ll * has [ch [x][0]] * base [tson [ch [x][1]] + 1] + 1ll * lav [x] * base [tson [ch [x][1]]] + 1ll * has [ch [x][1]] ) % M ;
}
inline void spinup( int x ){
R y = fa [x] , z = fa [y] , k = sk ( x ) ;
ch [z][sk(y)] = x , fa [x] = z ;
ch [y][k] = ch [x][k ^ 1] , fa [ch [x][k ^ 1]] = y ;
ch [x][k ^ 1] = y , fa [y] = x ;
zt( y ) , zt( x ) ;
}
inline void splay( int x , int pos ){
while( fa [x] != pos ){
R y = fa [x] , z = fa [y] ;
if( z != pos ) sk( x ) ^ sk( y ) ? spinup( x ) : spinup( y ) ;
spinup( x ) ;
} if( !pos ) root = x ;
}
inline void ins( int dlt , int vl ){
R now = root , fh = 0 ;
while( now && val [now] != dlt ) fh = now , now = tr( now , dlt ) ;
if( now ) size [now] ++ ;
else{
now = ++ znt ;
fa [now] = fh , val [now] = dlt , lav [now] = vl ;
son [now] = size [now] = 1 ;
if( fh ) tr( fh , dlt ) = now ;
} splay( now , 0 ) ;
}
inline int rk( int rank ){
R now = root ;
if( son [now] < rank ) return 0 ;
while( 1 ){
R nxt = ch [now][0] ;
if( son [nxt] + size [now] < rank ) rank -= ( son [nxt] + size [now] ) , now = ch [now][1] ;
else if( son [nxt] >= rank ) now = nxt ;
else return now ;
}
}
inline void Cge(){
x = rk( x + 1 ) , splay( x , 0 ) ;
lav [x] = bt [0] - 'a' , zt( x ) ;
}
inline void Ins(){
int pre = rk( x + 1 ) , nxt = rk( x + 2 ) ;
splay( pre , 0 ) , splay( nxt , pre ) ;
ch [nxt][0] = ++ znt ;
fa [znt] = nxt , lav [znt] = bt [0] - 'a' ;
son [znt] = tson [znt] = size [znt] = 1 ;
splay( znt , 0 ) , len ++ ;
}
void sc(){
ios::sync_with_stdio( false ) ;
scanf( "%s" , s + 1 ) , m = read() ;
base [0] = 1 ; for( R i = 1 ; i < N ; i ++ ) base [i] = ( 1ll * base [i - 1] * P ) % M ;
}
inline bool check( int s ){
int bx = rk( x ) , by = rk( y ) ;
int idx = rk( x + s + 1 ) , idy = rk( y + s + 1 ) ;
splay( bx , 0 ) , splay( idx , bx ) ;
int hsx = has [ch [ch [root][1]][0]] ;
splay( by , 0 ) , splay( idy , by ) ;
int hsy = has [ch [ch [root][1]][0]] ;
return hsx == hsy ;
}
inline void lcqwq( int x , int y ){
int idx = rk( x + 1 ) , idy = rk( y + 1 ) ;
int lside = 2 , rside = len , ans = ( lav [idx] == lav [idy] ) ;
while( lside <= rside ){
int mid = ( lside + rside ) >> 1 ;
if( mid + max( x , y ) > son [root] - 1 ){ rside = mid - 1 ; continue ; }
if( check( mid ) ) ans = mid , lside = mid + 1 ;
else rside = mid - 1 ;
} printf( "%ld\n" , ans ) ;
}
void work(){
ins( -Inf , 0 ) , ins( Inf , 0 ) , len = strlen( s + 1 ) ;
for( R i = 1 ; i <= len ; i ++ ) ins( i , s [i] - 'a' ) ;
while( m -- ){
scanf( "%s", kk ) ;
if( kk [0] == 'Q' ) x = read() , y = read() , lcqwq( x , y ) ;
if( kk [0] == 'R' ) x = read() , scanf( "%s" , bt ) , Cge( ) ;
if( kk [0] == 'I' ) x = read() , scanf( "%s" , bt ) , Ins( ) ;
}
}
signed main(){
sc() ;
work() ;
return 0 ;
}

浙公网安备 33010602011771号