初等字符串
$CuO + CO \triangleq Cu + CO_2 $
初等字符串
字符串Hash
\(\bf{Hash}:\)一种好用又cd的算法
\(·First\)
如果要比较两个字符串的大小,开\(string\)两两比较是\(O(n)\)の算法如果进行\(m\)次比较的话$O( m \times n ) $显然去世
考虑\(O(m)\)の算法 , 即让比较过程变为\(O(1)\)
那么如果可以将字符串表示成一个数,那么就好说很多了。
如何用k进制来表示一个数?
\(eg:\)
......
其实字符串也可以这么表示,最后の值叫他 Hash 值
对于字符串\(s_1s_2s_3.....s_n\)
我们可以用\(hash_s\)表示
那么\(hash_s:\)
$$ hash_s = s_1*k^{n-1} \times s_2 \times k ^{n-2} \times s_3 \times k^{n-3} \times ... \times s_n \times k^0 $$
其中\(k\)是一个\(\ge\)字符串中最大字符大小の数\((例:Cekasauk中の's')\),最好是个素数(前置知识·素数)
我用\(131\)较习惯
此时是不是觉得\(hash\)非常好用?
\(\mathrm{·美中不足:\color{red}哈希冲突}\)
\(\because\)对于字符来说,\(0\)的值是\(48\)
\(\therefore\)任一其他字符都会很大
如果我在每一个字符上都乘上一个\(131^\alpha\)次方,再乘起来 , 很有可能爆 $long \ long $
\(\therefore\)在计算字符串哈希值时,再摸一个大整数(最好是素数)
这时就可以知道一个字符串の\(hash\)值了。
\(\color{blue}But:\)
$ mod \ p $ 带来の问题十分明显了。
如果两个字符串不相等,但\(mod\)让他们整好相等了,\(hash\)值返回的就是\(WA\)的。
这就叫做哈希冲突。
哈希冲突无法避免,但可以考虑优化。
$·考虑优化\implies second : \ hash \ の优化 $
-
双模数哈希
模一个数\(p\)如果不准的话就再模一个数\(q\),且\(gcd( \ p \ , \ q \ ) \ = 1\)
精准很多
-
$ unsigned \ long \ long $
如果模数太小导致哈希冲突の话,就想方法让模数尽可能的大。
$ unsigned \ long \ long $ : 是很不错的选择。
\(2^{64}-1の超大存储量\) ,
oppo23,模出你的美$ but $ 永远不是完美的
$ unsigned \ long \ long $ 是一个 $ \rm{很好卡的数值} $。
出题人会卡死你。
但平常の题用它还是很不错的。 -
哈希表
(详见\(Fifth\))
$ code:(\ of \ unsigned \ long \ long \ ) $
点击查看代码
#define int unsigned long long
using namespace std ;
const int down = 131 //底数
int does[ N ] ; // down^k
string s ; // 模式串
string b[ N ] ;
int n ;
int hash_s , hasher[ N ] ;
inline void Make_Hash( string s , int &hashwer )
{
int len = s.size( ) ;
for ( int i = 0 ; i < len ; ++ i )
{
hashwer += s[ i ] * does[ n - i + 1 ] ; // 看做从一开始
}
}
signed main( )
{
cin >> n ;
cin >> s ;
Make_Hash( s , hash_s ) ;
for( int i = 1 ; i <= n ; ++ i )
{
cin >> b[ i ] ;
Make_Hash( b[ i ] , hasher[ i ] ) ;
if( hasher[ i ] == hash_s )
cout << "Yes" << '\n' ;
else cout << "Fucking Bich " << '\n' ;
}
}
\(·Third - hash \ 求前缀和\)
如果只能比较单个字符串,这个算法就太\(\color{white}逊\)了。
\(\therefore\) 这里有这么个题:
给定模式串b , 目标串s , 求在目标串中的前缀[1,r]是否等于b
\(s\)中前\(i\)个元素
你要让\(b = \!\!= s[ \ 1 \ , \ r \ ]\)
那么b.size( )一定 \(=\!\!=\) s[ 1 , r ].size( )
让\(hash_b\)左右同时乘$k^{ n - i } $
\(\because\)模过了数 $ \ \ \ $ \(\therefore\) 不能再除了\((不要和我一样sb)\)
\(\therefore\)只需要比较 \(hash_i\) $ and $ $ hash_b \times k ^{ n - i } $
$ ·Fourth $
求在目标串中与模式串相等の个数
运用\(Thrid\)の知识,自己想(md烦不想写)
·\(Fifth\) 哈希冲突の另一个解决方法
( 东西比较多 , 放这里讲 )
\(\bf{Hash表}\) 一种不是很神奇,但有时很给力。
把它作用说一下:
当查询这个\(Hash\)值代表的字符串时,查询这一个\(Hash\)值所代表的所有字符串。
类比一下:
当你在查字典时:
比如你想查蔡这个字时,你按照cai这个音去寻找,
但是发现cai这个地方第一个字是菜。
cai就相当于那个\(Hash\)值,此时就相当于哈希冲突了。
你在思索解决方法:
- 一个字一页
这种想法属实\(GS\),太废纸了。
- 接着往下翻
翻不了几页就到蔡了。
一页之后才是一页,是一个链式的结构。
哈希表就是这样了,对于\(Hash\)值相同时,用链将所有此串连起来。
·\(code\)
点击查看代码
struct Hash_Table_Of_List
{
struct One_node
{
int key_save ;
int value , next ;
} ;
One_node grass[ CuFeO4 << 1 ] ;
int head[ CuFeO4 << 1 ] , cnt ;
int Hash_return( int key )
{
return ( key % mod + mod ) % mod ;
}
int& operator[]( int key_in )
{
int inlist = Hash_return( key_in ) ; // 获取头指针
for (int i = head[ inlist ] ; i; i = grass[ i ].next )
if ( grass[ i ].key_save == key_in )
return grass[ i ].value ;
return grass[ ++ cnt ] = ( One_node ){ key_in, 0, head[ inlist ] } , head[ inlist ] = cnt , grass[ cnt ].value ;
}
Hash_Table_Of_List( )
{
cnt = 0;
memset( head , 0 , sizeof( head ) ) ;
}
}hasher;
当然还用另一种哈希表速度还要快一点,就是当此时此位被占后,就往下接着数,直到没有元素的位置,将此个放进去
最后插一句,最好的还是平板电视。
$ ·小结 $
在做字符串的题时,\(Hash\)是一个很给力的算法。
但他是很危的。\(Hash\)出题人想卡就能被卡掉。
在当下\(Hash\) 是一个\(dying\)的算法
只要知道你的模数,无论你的底数,都能卡掉你。(参见 \(Hash \ killer_2\) )
如果\(Hash \ killer_3\)被人AC了的话
那么这个算法,
\(\bf{我们真的要和她说再见了...}\)
所以,
珍惜当下,这个好用的算法
\(\bf{毕竟错过了,就不会再有了}\)
KMP
·赘述
说实话,不是一个很好理解的算法。
- \(KMP\)( $ D.E.Knuth $ ,$ J.H.Morris$ $ and $ $ V.R.Pratt $ )
是三个科学家提出的,\(\bf{不要理解错误!}\)
例题:
求在目标串中与模式串相等の个数,并输出左右端点。
当然可以用\(Hash\) $ , O( \ n \ ) $ 算法也很棒棒了
但 $ \ . \ . \ . \ . \ . \ . \ $
如果卡你 怎么办
\(\color{yellow}KMP,你值得拥有\)
$border $(可能拼错了 $ \ \ \ $ \([:-)]\) $ \ \ \ $ ):前缀和后缀相等
\(eg.\) $ \color{red}{114514}$ \(1919\) \(\color{red}114514\)
\(Next_i:\)字符串s中$[ \ 1 \ , \ i \ ] $ 前缀中最长的\(border\)
·$First \ $ 求\(Next\)
1.简述
首先定义$ Next_1 = 0 $
从\(Next_2\)开始遍历。
定义\(j\)指向\(Next_i\)表示当前\(i\)指向の\(Next\)
让\(j\)和\(i\)一起向上跳
并让$s_{j+1} = s_i $
如果不等,\(j=Next_j\)去求真正最大的\(border\)
2.形述
仔细想一想,\(i\)肯定由\(i-1\)递过来
当$ j + 1 $ 的字符值与$ i $相等时,那肯定要它呀。
但如果不是,对于你这个真正要求的\(Next_i\)来说,肯定是是在 [ 1 , j + 1 ) 中 。 ( \(\because\) $ j + 1 $ 无法取,肯定不在$ j + 1 $外 )
对于这个\(j\)它是\(Next_{i-1}\)
$ i - 1 $ 后面加了一个字符,所以这个新的最长 \(border\) 肯定是原来的一个 \(border\) 后多一个等于 \(i\) 字符值的字符。
如果这个$ Next_j \ + \ 1 $ 必须得字符值等于 \(i\)
$code \ of \ asking \ for \ Next_i $
点击查看简短的代码
for( int i = 2 , j = 0 ; i <= len ; ++ i )
{
while ( j && s[ i ] != s[ j + 1 ] ) j = Next[ j ] ;
if( s[ i ] == s[ j + 1 ] ) j ++ ;
Next[ i ] = j ;
}
肯定会有人质疑这个算法的时间复杂度(\(somebody \ have \ said \ : \ md \ 一眼 \ n^2 \ 嘛!\))
但是很抱歉,\(\bf{它不是}\)。
\(j\) 最多到 \(n\) , 但在其中 \(j\) 处于单调递减状态,\(\therefore\) $ while $ 循环最多执行\(n\)次。
因此复杂度为\(O( \ n \ )\)
//\(注意\) \(Next\) 中 N大写
\(·second\) 匹配串
其实代码长相和前面的几乎一模一样
1. \(Next_i\)在这里の用处
用jb想一想也是求模式串的\(Next\)呀亲
2.实现
用 $ j $ 表示目前模式串跳到的位置,$ i $ 表示目标串跳到的位置
循环时枚举的肯定是\(i\)
如果此时\(s_i = t_{j+1}\)那么肯定是要它的。但如果不等了怎么办?
让\(j\)回到\(Next_j\)那么如果此时还不行接着跳。
直至$s_i = t_{j+1} \ || \ $ 将\(j\)排空
当你判完后(已经find完一个匹配串)我不想修改\(j\)呢\(\color{red}:-)\)
在让\(j=Next_j\)的时候多加一种\(j=\!\!\!=n\)の情况
$code \ of \ matching \ strings $
点击查看代码
for ( int i = 1 , j = 0 ; i <= len ; ++ i ) // t是目标串,s是模式串,len是t长,n是s长
{
while ( j && ( j == n || t[ i ] != s[ j + 1 ] ) ) j = Next[ j ] ;
if( t[ i ] == s[ j + 1 ] )
{
j ++ ;
}
if( j == n )
{
cout << i - j + 1 << ' ' << j << '\n' ;
}
}
例题
poj2752
hzoj,Seek the Name, Seek the Fame
简化题意:给一个目标串,求其所有のborder长度,按增序输出
adcad (ad、adcad) => 2 5
考察对\(Next\)的理解
只需要求\(Next_i、Next_{Next_i}、Next_{Next_{Next_i}} ... 求到0前\)
注意输出总长。
uojNOI2014动物园
简化题意:
定义num[ i ] 为在长度为i的前缀,没有任何重叠的border的个数
求:
考虑\(num\)与\(Next\)的关系。
定义一个\(numer_i\) 为长度为\(i\)的前缀中\(border\)的个数。
易求出:$numer_i = numer_{next_i} + 1 $
对于这个\(num\)来说,其中最长的长度是 $\leq \ i \div 2 $
\(\therefore\) 对于此时我们可以对于一个\(j\)如果不符合就使之变为\(Next_j\)
具体看代码:
$ code : $
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std ;
const int N = 1e6 + 100 ;
int n ;
int Next[ N ] ;
char s[ N + 11 ] ;
int num[ N ] , numer[ N ] ;
const int mod = 1000000007 ;
inline int read( )
{
int x = 0 , f = 1 ;
char c = getchar( ) ;
while ( c < '0' || c > '9' )
{
if( c == '-' )
{
f = - f ;
}
c = getchar( ) ;
}
while ( c >= '0' && c <= '9' )
{
x = x * 10 + c - '0' ;
c = getchar( ) ;
}
return x * f ;
}
signed main( )
{
n = read( ) ;
while( n -- )
{
memset( Next , 0 , sizeof ( Next ) ) ;
memset( num , 0 , sizeof ( num ) ) ;
memset( numer , 0 , sizeof( numer ) ) ;
cin >> s + 1 ;
int len = strlen( s + 1 ) ;
Next[ 1 ] = 0 ;
int ans = 1 ;
int jj ;
numer[ 1 ] = 1 ;
for ( int i = 2 , j = 0 ; i <= len ; ++ i )
{
while ( j && s[ j + 1 ] != s[ i ] ) j = Next[ j ] ;
if( s[ j + 1 ] == s[ i ] ) ++ j ;
Next[ i ] = j ;
numer[ i ] = ( numer[ j ] + 1 ) % mod ;
}
for( int i = 2 , j = 0 ; i <= len ; ++ i )
{
while ( j && s[ j + 1 ] != s[ i ] ) j = Next[ j ] ;
if( s[ j + 1 ] == s[ i ] ) ++ j ;
while( j * 2 > i ) j = Next[ j ] ;
//cout << numer[ j ] << endl ;
ans = ( ans * ( numer[ j ] + 1 ) % mod ) % mod ;
}
cout << ans << '\n';
}
}
\(小结\)
\(KMP\)在大多数情况下时非常好用的,你并没有能够卡掉它的方法
$O( \ n \ ) $ 的复杂度也令人垂涎三尺。
为了在迟暮之年不会忘了自己曾经学过这个算法,于此写下题解。
Manacher (马拉车算法)
- 由来
科学家\(Manacher\)提出的算法
\(·First\) 赘述
马拉车解决的问题:
求一个字符串中各个回文串的长度
正反哈希再加上二分答案,$O( \ n \ log \ n \ ) $的算法其实已经够了。
但是,马拉车提供了更加优的算法,远超并碾压其他的求解此题的的算法
以$O( \ n \ ) $の司马复杂度傲视群雄。
\(·second\) 算法分析
·\(일\) -- 提前の处理
回文串有几种不同的类型,$sach \ as $ cxxxc \(||\) cxxc .
对于前面一种来说 , 回文中心是最中间的x,但对于后一种长度为偶数的来说回文中心在xx中间。
我去这怎么处理啊?
诶,对咯。牛波一的人经常有,\(OI\)特别多。
你也不想一下,如何不改变回文串的回文性质,且使回文中心在字符串上呢?
在原序列中的每两个字符间和开头结尾都填上#字符,这样\(OK\)
特别的,为了后面的操作,使最开头是@ , 结尾是/0 .
有问题的:
1. 如果在你的串中用过新添加的字符怎么办?
sb,用个别的呗。
2.如果键盘上所有的字符都包括在字符串内怎么办?
......\(\bf{难绷}\)
·\(이\) -- 核心
\(O(n)\)肯定只枚举一个值,大概猜一猜也知道是枚举中间の中心\(Centre\)
\(p_i\)存储以\(i\)为中心,\(p_i\)长为半径の半径长。
\(R\)表示目前所到最远处。
在枚举\(i\)时需要考虑:
- 如果\(i\)在\(R\)左:
其实 \(i\)的回文长度与 \(i\) 由 \(Centre\) 对称到 \(Centre\) 左端的对称点 $ Centre \times 2 - i $ 有着很大关系。
但并不是完全相等的。
因为你\(i\)后面还有在\(R\)后的字符,对称点左端最左也是\(R\)的对称点了。
\(\therefore\) 转移一:
然后暴力跳,最后看看 \(i\) 的右端点和目前的 \(R\) 的关系,如果前者远,\(Centre\) 变为 \(i\)
·$code \ of \ 板子 $
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std ;
const int N = 2.1*1e7 + 100 ;
char s[ N ] , so[ 2 * N ] ; int len ;
int p[ N ] , Centre = -1 , R = -1 ;
signed main( )
{
cin >> s ;
len = strlen( s ) ;
so[ 0 ] = '@' , so[ 1 ] = '#' ;
int j = 2 ;
for( int i = 0 ; i < len ; ++ i )
{
so[ j ++ ] = s[ i ] ;
so[ j ++ ] = '#' ;
}
so[ j ] = '\0' ;
len = j ; int ans = - 1 ;
for( int i = 1 ; i < len ; ++ i )
{
if( i < R ) p[ i ] = min( p[ Centre * 2 - i ] , R - i ) ;
else p[ i ] = 1 ;
while ( so[ i + p[ i ] ] == so[ i - p[ i ] ] ) p[ i ] ++ ;
if( i + p[ i ] > R )
{
R = i + p[ i ] ;
Centre = i ;
}
ans = max( ans , p[ i ] - 1 ) ;
}
cout << ans ;
}
·小结
马拉车好像不是提高知识,考的也不是很多...
所以, 理解就好 $ [ : - ) ] $
Tire 树
· 赘述
\(OJ\)使用不当害死人啊。。。(关于\(OJ\)上写的是tire树...)
咳咳,进入正题。
·\(First\) -- 建树||插入
\(Trie树\) 一种神奇的数据结构,又名字典树。
可以把它的运算模式看做查字典。
现在有han,mo,hangry几个单词来建字典树。
你发现han&hangry都包含han
\(so\) 在 n 处标一个标记 , hangry在这个基础上继续搜。
树:
(旁边表**意思是标记)
0
/ \
m h
| |
*o* a
|
*n*
|
g
|
r
|
*y*
$ code \ of \ insert $
点击查看代码
struct chain_trie_tree
{
int to , next ;
char price ;
chain_trie_tree( )
{
to = next = price = 0 ;
}
} ;
struct Trie_Tree
{
chain_trie_tree e[ 40 * N ] ;
int head[ 40 * N ] , cnt ;
void insert( char Checkee[ ] , int Point_at , int Last_Point , int place ) // 开始于根
{
bool flag = 0 ;
for( int i = head[ place ] ; i ; i = e[ i ].next )
{
int To_go = e[ i ].to ;
int charee = e[ i ].price ;
if( charee == Checkee[ Point_at ] )
{
flag = 1 ;
if( Last_Point == Point_at ) pd[ To_go ] ++ ;
else
{
insert( Checkee , Point_at + 1 , Last_Point , To_go ) ;
}
return ;
}
}
if( flag == 1 ) return ;
cnt ++ ;
e[ cnt ].to = ++ numbol ;
e[ cnt ].price = Checkee[ Point_at ] ;
e[ cnt ].next = head[ place ] ;
head[ place ] = cnt ;
if( Last_Point == Point_at )
{
pd[ numbol ] ++ ;
return ;
}
insert( Checkee , Point_at + 1 , Last_Point , numbol ) ;
}
Trie_Tree( )
{
tail = 0 ;
cnt = 0 ;
memset( head , 0 , sizeof( head ) ) ;
}
inline void clear( )
{
flag = 0 ;
cnt = 0 ;
memset( head , 0 ,sizeof( head ) ) ;
}
}tree ;
·\(Second\) -- 查询
其实和插入差不多,具体看代码
·\(code\)
点击查看代码
inline void print_word( int tail )
{
for( int i = 1 ; i <= tail ; ++ i )
{
cout << e[ stac[ i ] ].price ;
}
cout << '\n' ;
}
void find_word( int x )
{
if( head[ x ] == 0 )
{
tail = 0 ;
return ;
}
for( int i = head[ x ] ; i ; i = e[ i ].next )
{
int y = e[ i ].to ;
stac[ ++ tail ] = i ;
if( pd[ y ] )
{
print_word( tail ) ;
}
find_word( y ) ;
}
}
void checkol_string( int point )
{
for( int i = head[ point ] ; i ; i = e[ i ].next )
{
int y = e[ i ].to ;
if( pd[ y ] )
{
if( ( !head[ y ] && pd[ y ] > 1 ) || head[ y ] )
{
cout << "NO" << '\n' ;
flag = 1 ;
}
return ;
}
checkol_string( y ) ;
if( flag == 1 ) return ;
}
return ;
}
·例题
\(Phone \ List\)
\(code\)
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define together ios::sync_with_stdio( 0 ) ; cin.tie( 0 ) ; cout.tie( 0 )
using namespace std ;
const int N = 4e5 + 100 ;
int numbol ;
int pd[ N * 50 ] ;
queue<char>q ;
namespace Trie
{
struct chain_trie_tree
{
int to , next ;
char price ;
chain_trie_tree( )
{
to = next = price = 0 ;
}
} ;
struct Trie_Tree
{
chain_trie_tree e[ 40 * N ] ;
int head[ 40 * N ] , cnt ;
void insert( char Checkee[ ] , int Point_at , int Last_Point , int place ) // 开始于根
{
bool flag = 0 ;
for( int i = head[ place ] ; i ; i = e[ i ].next )
{
int To_go = e[ i ].to ;
int charee = e[ i ].price ;
if( charee == Checkee[ Point_at ] )
{
flag = 1 ;
if( Last_Point == Point_at ) pd[ To_go ] ++ ;
else
{
insert( Checkee , Point_at + 1 , Last_Point , To_go ) ;
}
return ;
}
}
if( flag == 1 ) return ;
cnt ++ ;
e[ cnt ].to = ++ numbol ;
e[ cnt ].price = Checkee[ Point_at ] ;
e[ cnt ].next = head[ place ] ;
head[ place ] = cnt ;
if( Last_Point == Point_at )
{
pd[ numbol ] ++ ;
return ;
}
insert( Checkee , Point_at + 1 , Last_Point , numbol ) ;
}
void BFS_print_tree( )
{
q.push( 0 ) ;
while( !q.empty( ) )
{
int x = q.front( ) ; q.pop( ) ;
for( int i = head[ x ] ; i ; i = e[ i ].next )
{
cout << x << "->" << e[ i ].to << ":" << e[ i ].price << endl ;
q.push( e[ i ].to ) ;
}
}
}
int stac[ N ] , tail ;
inline void print_word( int tail )
{
for( int i = 1 ; i <= tail ; ++ i ) //送你个密码:chengxiao
{
cout << e[ stac[ i ] ].price ;
}
cout << '\n' ;
}
void find_word( int x )
{
if( head[ x ] == 0 )
{
tail = 0 ;
return ;
}
for( int i = head[ x ] ; i ; i = e[ i ].next )
{
int y = e[ i ].to ;
stac[ ++ tail ] = i ;
if( pd[ y ] )
{
print_word( tail ) ;
}
find_word( y ) ;
}
}
int flag = 0 ;
void checkol_string( int point )
{
for( int i = head[ point ] ; i ; i = e[ i ].next )
{
int y = e[ i ].to ;
if( pd[ y ] )
{
if( ( !head[ y ] && pd[ y ] > 1 ) || head[ y ] )
{
cout << "NO" << '\n' ;
flag = 1 ;
}
return ;
}
checkol_string( y ) ;
if( flag == 1 ) return ;
}
return ;
}
Trie_Tree( )
{
tail = 0 ;
cnt = 0 ;
memset( head , 0 , sizeof( head ) ) ;
}
inline void clear( )
{
flag = 0 ;
cnt = 0 ;
memset( head , 0 ,sizeof( head ) ) ;
}
}tree ;
}
using namespace Trie ;
int T , n ;
char s[ N ][ 101 ] ;
signed main( )
{
#ifndef ONLINE_JUDGE
freopen( "cjs.in" , "r" , stdin ) ;
freopen( "cjs.out" , "w" , stdout ) ;
#endif
together ;
cin >> T ;
while ( T -- )
{
tree.clear( ) ;
cin >> n ;
for ( int i = 1 ; i <= n ; ++ i )
{
cin >> s[ i ] + 1 ;
tree.insert( s[ i ] , 1 , strlen( s[ i ] + 1 ) , 0 ) ;
}
//tree.BFS_print_tree( ) ;
tree.checkol_string( 0 ) ;
if( tree.flag == 0 )
{
cout << "YES" << '\n' ;
}
}
}
·\(Third\)--\(01Trie\)
异或是基于\(2\)进制的,所以如果将二进制的树扔进\(Trie\)树里,可以完成一些神奇的操作。
·例:
$ The \ XOR \ Largest \ Pair $
解:
将所有的数按照\(01\)字符串方式插入字典树。
以每一个\(i\)为主,观察每一层:
如果有与这个相反的边,就从那边走,否则直接下。
·\(code\)
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define together ios::sync_with_stdio( 0 ) ; cin.tie( 0 ) ; cout.tie( 0 )
using namespace std ;
const int N = 1e7 + 100 ;
int n , trie[ N * 2 ][ 2 ] ;
int a[ N ] ; int tot ;
void insert( int v )
{
int p = 0 ;
for( int i = 31 ; i >= 0 ; -- i )
{
int l = ( v >> i ) & 1 ;
if( !trie[ p ][ l ] )
trie[ p ][ l ] = ++ tot ;
p = trie[ p ][ l ] ;
}
}
int find( int v )
{
int p = 0 ;
int sum = 0 ;
for( int i = 31 ; i >= 0 ; -- i )
{
int l = ( v >> i ) & 1 ;
if( trie[ p ][ l ^ 1 ] )
{
p = trie[ p ][ l ^ 1 ] ;
sum += 1 << i ;
}
else
{
p = trie[ p ][ l ] ;
}
}
return sum ;
}
signed main( )
{
#ifndef ONLINE_JUDGE
freopen( "qzs.in" , "r" , stdin ) ;
freopen( "qzs.out" , "w" , stdout ) ;
#endif
together ;
cin >> n ;
for( int i = 1 ; i <= n ; ++ i )
{
cin >> a[ i ] ;
insert( a[ i ] ) ;
}
int ans = - 114514 ;
for( int i = 1 ; i <= n ; ++ i )
{
ans = max( ans , find( a[ i ] ) ) ;
}
cout << ans ;
}
·\(Fourth\)--可持久化字典树
\(NOI\)知识点,浅说一下。
如果想查修改前的历史版本,就需要这个数据结构。
在新的上面建新的,并演出新根。
·小结
字典树是一个较重要的知识点,虽然理解不难,但一定要明白。
·结尾散花\(\color{pink}✿✿ヽ(°▽°)ノ✿\)
完成不易......

浙公网安备 33010602011771号