題解:
我們考慮用線段樹維護最小後綴的出現位置,那麼需要考慮如何合併兩個區間
發現最小後綴可能是前面區間的某一個後綴加上後面的一整個字符串,注意這個某一個後綴並不一定是前面區間的最小後綴,於是我們需要維護可能後綴的集合。
定義 :一個 i i i 的 “k k k -後綴” 指的是字符串 S [ i . . . k ] S[i...k] S [ i . . . k ] (以下用 S i S_i S i 表示),一個 i i i 的 k k k -後綴是好的當且僅當在 k k k 後方可以添加一個字符串 T T T 使得 S [ i . . . k ] + T ≤ S [ j . . . k ] + T ( ∀ j ) S[i...k]+T\le S[j...k]+T(\forall j) S [ i . . . k ] + T ≤ S [ j . . . k ] + T ( ∀ j )
那麼我們需要做的就是維護好的 k k k 後綴集合
引理 :令 i i i 的 k k k 後綴的長度爲 ∣ i ∣ = k − i + 1 |i|=k-i+1 ∣ i ∣ = k − i + 1 ,如果 i , j i,j i , j 均是好的 k k k 後綴,那麼有 ∣ i ∣ ≥ 2 ∗ ∣ j ∣ |i|\ge 2*|j| ∣ i ∣ ≥ 2 ∗ ∣ j ∣
證明 :考慮反證法,不妨令 ∣ i ∣ < 2 ∗ ∣ j ∣ |i|<2*|j| ∣ i ∣ < 2 ∗ ∣ j ∣ ,首先 j j j 的 k k k 後綴是 i i i 的 k k k 後綴的前綴,那麼容易發現 i i i 的 k k k 後綴有一個長度爲 ∣ i ∣ − ∣ j ∣ |i|-|j| ∣ i ∣ − ∣ j ∣ 的循環,設 T T T 可以使 S j S_j S j 爲最小後綴,那麼有 S i + T ≥ S j + T S_i+T\ge S_j+T S i + T ≥ S j + T ,
去掉 ∣ i ∣ − ∣ j ∣ |i|-|j| ∣ i ∣ − ∣ j ∣ 的循環,有 S j + T ≥ S j + ∣ i ∣ − ∣ j ∣ + T S_j+T\ge S_{j+|i|-|j|}+T S j + T ≥ S j + ∣ i ∣ − ∣ j ∣ + T 與 S j S_j S j 爲最小後綴矛盾
有了這個引理,我們可以知道一個好的 k k k -後綴集合大小是 l o g ∣ S ∣ log|S| l o g ∣ S ∣ 的,於是我們可以用線段樹維護這麼一個集合,單次修改需要合併 l o g ( n ) log(n) l o g ( n ) 次,合併需要支持比較兩個後綴的大小,比較 l o g ( n ) log(n) l o g ( n ) 次,考慮二分 + h a s h hash h a s h ,如果能把 h a s h hash h a s h 查詢做到 O ( 1 ) O(1) O ( 1 ) ,那麼複雜度就是 O ( M l o g ( n ) 3 ) O(Mlog(n)^3) O ( M l o g ( n ) 3 ) ,於是我們分塊維護 h a s h hash h a s h (前綴和) 即可,複雜度 O ( n l o g ( n ) 2 + M l o g ( n ) 3 + M n ) O(nlog(n)^2+Mlog(n)^3+M\sqrt n) O ( n l o g ( n ) 2 + M l o g ( n ) 3 + M n )
主要突破口還是基於考慮好後綴的合併,然後發現更多的性質,挺巧妙的!
代碼也不難寫 ,寫完之後一發得了 70,不知道爲什麼 _ _ u i n t 128 _ t \_\_uint128\_t _ _ u i n t 1 2 8 _ t 才能過而 u n s i g n e d l o n g l o n g unsigned\ long \ long u n s i g n e d l o n g l o n g 不行,可能是衝突了之類的(霧)
using namespace std;
int read( ) {
int cnt = 0, f = 1; char ch = 0;
while( ! isdigit( ch)) { ch = getchar( ) ; if( ch == '-' ) f = -1; }
while( isdigit( ch)) cnt = cnt*10 + ( ch-'0' ) , ch = getchar( ) ;
return cnt * f;
}
cs int N = 2e5 + 50;
typedef __uint128_t ull;
int n, m;
namespace Hash{
int blk[ N] , l[ N] , r[ N] , vl[ N] , add[ N] , ct;
ull pw[ N] , Spw[ N] , sum[ N] , tag[ N] ;
cs int Base = 1e9 + 7;
void Build( ) {
int S = sqrt( n) ;
for( int i = 1; i <= n; i++) blk[ i] = ( i-1) / S + 1;
for( int i = 1; i <= n; i += S) l[ ++ct] = i, r[ ct] = i + S - 1; r[ ct] = n;
pw[ 0] = Spw[ 0] = 1;
for( int i = 1; i <= n; i++)
pw[ i] = pw[ i-1] * Base, Spw[ i] = Spw[ i-1] + pw[ i] , sum[ i] = sum[ i-1] + ( ull) vl[ i] * pw[ i] ;
}
void modify( int ql, int qr, int d) {
int pl = blk[ ql] , pr = blk[ qr] ;
if( pl + 1 >= pr) {
for( int i = ql; i <= qr; i++) {
vl[ i] += d;
sum[ i] += d * ( Spw[ i] - Spw[ ql - 1] ) ;
}
}
else{
for( int i = ql; i <= r[ pl] ; i++)
vl[ i] += d, sum[ i] += d * ( Spw[ i] - Spw[ ql - 1] ) ;
for( int i = pl + 1; i < pr ; i++)
add[ i] += d, tag[ i] += d * ( Spw[ l[ i] - 1] - Spw[ ql - 1] ) ;
for( int i = l[ pr] ; i <= qr; i++)
vl[ i] += d, sum[ i] += d * ( Spw[ i] - Spw[ ql - 1] ) ;
}
ull dlt = d * ( Spw[ qr] - Spw[ ql - 1] ) ;
for( int i = qr + 1; i <= r[ pr] ; i++) sum[ i] += dlt;
for( int i = pr + 1; i <= ct; i++) tag[ i] += dlt;
}
ull Get( int x) { return sum[ x] + tag[ blk[ x] ] + add[ blk[ x] ] * ( Spw[ x] - Spw[ l[ blk[ x] ] -1] ) ; }
int val( int x) { return vl[ x] + add[ blk[ x] ] ; }
int lcp( int x, int y) {
if( x > y) swap( x, y) ;
int l = 0, r = n - y + 1; ull vx = Get( x - 1) , vy = Get( y - 1) ;
while( l < r) {
int mid = ( l+r+1) >> 1;
if (( Get( x + mid - 1 ) - vx) * pw[y - x] == Get( y + mid - 1 ) - vy) l = mid;
else r = mid - 1 ;
} return l;
}
}
struct data{
int l, r;
vector< int> S;
data( int _l = 0 , int _r = 0 ) { S.clear( ) ; l = _l; r = _r; }
};
data operator + ( cs data & A, cs data & B) {
data as( A.l, B.r) ;
for( int x : A.S) {
bool FLAG = true;
while( as.S.size( )) {
int y = as.S.back( ) ;
int lcp = Hash :: lcp( x, y) ;
if( x + lcp - 1 >= B.r) break ;
if( Hash :: val( x + lcp) > Hash :: val( y + lcp)) { FLAG = false ; break ; }
as.S.pop_back( ) ;
}
if( FLAG && ( as.S.empty( ) || B.r - x + 1 <= x - as.S.back( )) ) as.S.pb( x) ;
}
for( int x : B.S) {
bool FLAG = true ;
while( as.S.size( )) {
int y = as.S.back( ) ;
int lcp = Hash :: lcp( x, y) ;
if( x + lcp - 1 >= B.r) break ;
if( Hash :: val( x + lcp) > Hash :: val( y + lcp)) { FLAG = false ; break ; }
as.S.pop_back( ) ;
}
if( FLAG && ( as.S.empty( ) || B.r - x + 1 <= x - as.S.back( )) ) as.S.pb( x) ;
} return as;
}
namespace SGT{
cs int N = ::N << 2;
data vl[ N] ;
void pushup( int x) { vl[ x] = vl[ x<< 1] + vl[ x<< 1| 1] ; }
void build( int x, int l, int r) {
if( l == r) {
vl[ x] .l = l; vl[ x] .r = r;
vl[ x] .S.pb( l) ; return ;
}
build( x<< 1, l, mid) ; build( x<< 1| 1, mid+1, r) ;
pushup( x) ;
}
void modify( int x, int l, int r, int L, int R) {
if( L <= l && r <= R) return ;
if( L<= mid) modify( x<< 1, l, mid, L, R) ;
if( R> mid) modify( x<< 1| 1, mid+1, r, L, R) ; pushup( x) ;
}
data query( int x, int l, int r, int L, int R) {
if( L <= l && r <= R) return vl[ x] ;
if( R<= mid) return query( x<< 1, l, mid, L, R) ;
else if( L> mid) return query( x<< 1| 1, mid+1, r, L, R) ;
return query( x<< 1, l, mid, L, R) + query( x<< 1| 1, mid+1, r, L, R) ;
}
}
int main( ) {
n = read( ) , m = read( ) ;
for( int i = 1; i <= n; i++) Hash :: vl[ i] = read( ) + 5e8;
Hash :: Build( ) ;
SGT :: build( 1, 1, n) ;
while( m--) {
int op = read( ) , l = read( ) , r = read( ) ;
if( op == 1) {
int d = read( ) ;
Hash :: modify( l, r, d) ;
SGT :: modify( 1, 1, n, l, r) ;
}
if( op == 2)
cout << SGT :: query( 1, 1, n, l, r) .S.back( ) << '\n' ;
} return 0;
}