P4094 字符串
P4094 字符串
简化题意
给定字符串 \(s\), 每次询问给定两个字符串 \([a , b]\) 和 \([c , d]\), 求前串的所有子串和后串的最长公共前缀。
\(n \le 10 ^ 5 , m \le 10 ^ 5\)
题解
感觉其实这道题并不是特别难的,就是代码长,不折不扣的码农题。
刚开始有一个错误的想法,就是主席树维护 \(Rank\) 值查离 \(Rank_c\) 前驱后继两个值。
这个显然就是错的,因为可能匹配出的 \(LCP\) 值越过了 \(b\),导致可能前面的长度要更优。
正解是二分答案,当钦定答案为 \(mid\) 时,显然区间为 \([a , b - mid + 1]\)。
先二分 \(Rank_y\) 上下界,使上下界为第一个和最后一个与 \(y \ LCP \ge mid\) 的位置。
主席树上查在这个区间是否有值即可。
复杂度为 \(\mathcal{O}(n \log^2 n)\)。
不卡常。
Code
CODE
#include <bits/stdc++.h>
using namespace std ;
const int N = 1e5 + 100 ;
char s[N] ;
int n , M ;
namespace CharacterString {
namespace SuffixArray {
/*This is a algorithm in order to find suffixes and sort for them with the time complexity O(nlogn).
It supports the operation of getting the longest prefix.*/
int SA[N] , Rank[N] , m = 127 , p , Lstrk[N] , id[N] , cnt[N] , key1[N] ;
int lg[N] , ST[21][N] ;
inline void Suffix_Array() {
memset(cnt , 0 , sizeof(cnt)) ;
auto Compare = [](int x , int y , int j) {
return Lstrk[x] == Lstrk[y] && Lstrk[x + j] == Lstrk[y + j] ;
} ;
for (int i = 1 ; i <= n ; ++ i) cnt[Rank[i] = s[i]] ++ ;
for (int i = 1 ; i <= m ; ++ i) cnt[i] += cnt[i - 1] ;
for (int i = n ; i >= 1 ; -- i) SA[cnt[Rank[i]] --] = i ;
for (int j = 1 ; ; m = p , j <<= 1) {
p = 0 ;
for (int i = n ; i > n - j ; -- i) id[++ p] = i ;
for (int i = 1 ; i <= n ; ++ i) {
if (SA[i] - j > 0) {
id[++ p] = SA[i] - j ;
}
}
memset(cnt , 0 , sizeof(cnt)) ;
for (int i = 1 ; i <= n ; ++ i) {
++ cnt[key1[i] = Rank[id[i]]] ;
}
for (int i = 1 ; i <= m ; ++ i) cnt[i] += cnt[i - 1] ;
for (int i = n ; i >= 1 ; -- i) {
SA[cnt[key1[i]] --] = id[i] ;
}
memcpy(Lstrk , Rank , sizeof(Rank)) ; p = 0 ;
for (int i = 1 ; i <= n ; ++ i) {
Rank[SA[i]] = Compare(SA[i] , SA[i - 1] , j) ? p : ++ p ;
}
if (p == n) break ;
}
}
inline int MinST(int l , int r) {
int k = lg[r - l + 1] ;
return min(ST[k][l] , ST[k][r - (1 << k) + 1]) ;
}
inline int LCP(int l , int r) {
if (l == r) return n - l + 1 ;
if (Rank[l] > Rank[r]) swap(l , r) ;
return MinST(Rank[l] + 1 , Rank[r]) ;
}
inline void GetHeight() {
for (int i = 2 ; i <= n ; ++ i) lg[i] = lg[i >> 1] + 1 ;
for (int i = 1 , k = 0 ; i <= n ; ++ i) {
if (!Rank[i]) continue ;
if (k) k -- ;
while (s[i + k] == s[SA[Rank[i] - 1] + k]) ++ k ;
ST[0][Rank[i]] = k ;
}
for (int j = 1 ; j <= lg[n] ; ++ j) {
for (int i = 1 ; i <= n - (1 << j) + 1 ; ++ i) {
ST[j][i] = min(ST[j - 1][i] , ST[j - 1][i + (1 << (j - 1))]) ;
}
}
}
}
} using namespace CharacterString ;
using namespace SuffixArray ;
namespace Chairman_Tree {
#define lson(x) t[x].son[0]
#define rson(x) t[x].son[1]
#define data(x) t[x].data
#define mid ((l + r) >> 1)
struct Node {
int son[2] , data ;
} t[N * 200] ; int root[N] , numbol ;
void updata(int &now , int id , int l , int r , int x) {
t[now = ++ numbol] = t[id] ;
if (l == r) return t[now].data ++ , void() ;
if (x <= mid) updata(lson(now) , lson(id) , l , mid , x) ;
else updata(rson(now) , rson(id) , mid + 1 , r , x) ;
t[now].data = t[lson(now)].data + t[rson(now)].data ;
}
bool check(int fir , int sec , int l , int r , int x , int y) {
if (x <= l && r <= y) return (data(fir) - data(sec) != 0) ;
bool flag = 0 ;
if (x <= mid) flag |= check(lson(fir) , lson(sec) , l , mid , x , y) ;
if (y > mid) flag |= check(rson(fir) , rson(sec) , mid + 1 , r , x , y) ;
return flag ;
}
#undef mid
} using namespace Chairman_Tree ;
pair <int , int> Find(int now , int x) {
int left = 1 , right = now - 1 , ans1 = now , ans2 = now ;
while (left <= right) {
int mid = (left + right) >> 1 ;
if (LCP(SA[mid] , SA[now]) >= x) {
ans1 = mid ;
right = mid - 1 ;
} else left = mid + 1 ;
}
left = now + 1 , right = n ;
while (left <= right) {
int mid = (left + right) >> 1 ;
if (LCP(SA[mid] , SA[now]) >= x) {
left = mid + 1 ;
ans2 = mid ;
} else right = mid - 1 ;
}
return make_pair(ans1 , ans2) ;
}
signed main() {
// freopen("1.in" , "r" , stdin) ;
// freopen("1.out" , "w" , stdout) ;
ios::sync_with_stdio(0) , cin.tie(0) , cout.tie(0) ;
cin >> n >> M ;
for (int i = 1 ; i <= n ; ++ i) cin >> s[i] ;
Suffix_Array() ; GetHeight() ;
int ans = 0 ; pair <int , int> mp ;
for (int i = 1 ; i <= n ; ++ i) updata(root[i] , root[i - 1] , 1 , n , Rank[i]) ;
for (int i = 1 , w , x , y , z ; i <= M ; ++ i) {
cin >> w >> x >> y >> z ; ans = 0 ;
int left = 1 , right = min(x - w + 1 , z - y + 1) ;
while (left <= right) {
int mid = (left + right) >> 1 ;
mp = Find(Rank[y] , mid) ; int L = mp.first , R = mp.second ;
if (check(root[x - mid + 1] , root[w - 1] , 1 , n , L , R)) {
left = mid + 1 ; ans = mid ;
} else right = mid - 1 ;
}
cout << ans << '\n' ;
}
}