Loading

[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\)值,那么我们就可以通过二分长度得到答案。

具体过程是这样的

  1. 选定一个长度\(mid\)
  2. 得到\(\mathit{x,x+mid,y,y+mid}\)\(hash\)
  3. \(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\)

  1. 让原数减去\(x\)后面的值,设为\(x\), \(01212->01000\)
  2. 乘上禁止数,即左移一位, \(01000->010000\)
  3. 在加上\(x\),\(01000->010212\)
  4. 执行一个\(\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\)值的维护方法也比之前的简单多了

\[hash[x] = hash[ch[x][0]]*tson[ch [x][1]]+char[x]*(tson[ch[x][1]]-1)+hash[ch[x][1]] \]

然后直接比较就行了。

对于修改,把它\(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 ;
} 
posted @ 2021-07-09 14:51  Soresen  阅读(79)  评论(0)    收藏  举报