[HEOI2016/TJOI2016]字符串 (后缀数组+主席树+二分)
description
佳媛姐姐過生日的時候,她的小伙伴從某東上買了一個生日禮物。生日禮物放在一個神奇的箱子中。箱子外邊寫了一個長為 n 的字符串 s,和 m 個問題。佳媛姐姐必須正確回答這 m 個問題,才能打開箱子拿到禮物,升職加薪,出任 CEO,嫁給高富帥,走上人生巔峰。每個問題均有 a,b,c,d 四個參數,問你子串
s[a…b] 的所有子串和 s[c…d]的最長公共前綴的長度的最大值是多少?佳媛姐姐并不擅長做這樣的問題,所以她向你求助,你該如何幫助她呢?
輸入格式
輸入的第一行有兩個正整數 n,m,分別表示字符串的長度和詢問的個數。
接下來一行是一個長為 n 的字符串。字符串中僅有小寫英文字母。
接下來 m 行,每行有四個數 a,b,c,d,表示詢問 s[a…b] 的所有子串和 s[c…d]的最長公共前綴的最大值
輸出格式
對于每一次詢問,輸出答案
樣例
5 5 aaaaa 1 1 1 5 1 5 1 1 2 3 2 3 2 4 2 3 2 3 2 41 1 2 2 2數據范圍與提示
對于所有的數據,1≤n,m≤100000, a≤b, c≤d, 1≤a,b,c,d≤n
solution
S[a:b]S[a:b]S[a:b]的所有子串和S[c:d]S[c:d]S[c:d]整串的最長公共前綴
Step1
二分。。。
如果一個長度midmidmid可行,那么比midmidmid小的所有長度也都可行
即決策具有單調性,可以二分
Step2
如果二分的長度midmidmid可行,則這個串必然滿足
Step3
最長公共前綴,無腦上后綴數組,用RMQRMQRMQ查詢LCPLCPLCP
Step4
步驟二轉化為二元限制問題
解決方案:摁死一元,logloglog另一元
將后綴數組的rankrankrank排序后建立主席樹,查找區間[a,b?mid+1][a,b-mid+1][a,b?mid+1]
對rankrankrank建立主席樹是由于其LCPLCPLCP的特殊性質決定的
即LCPLCPLCP滿足條件的一定是一個連續區間,再套用二分,二分出區間的左右端點
code
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> using namespace std; #define INF 0x7f7f7f7f #define maxn 100005 struct node {int l, r, sum; }t[maxn << 5]; int n, m = 255, Q, cnt; int tot[maxn], sa[maxn], x[maxn], id[maxn], rnk[maxn << 1], h[maxn], root[maxn]; int st[maxn][25]; char s[maxn];void read( int &x ) {int f = 1;x = 0;char s = getchar();while( s < '0' || s > '9' ) {if( s == '-' ) f = -1;s = getchar();}while( '0' <= s && s <= '9' ) {x = ( x << 1 ) + ( x << 3 ) + ( s - '0' );s = getchar(); }x *= f; }void print( int x ) {if( x < 0 ) putchar( '-' ), x = -x;if( x > 9 ) print( x / 10 );putchar( x % 10 + '0' ); }void suffix() {for( int i = 1;i <= n;i ++ ) tot[x[i] = s[i]] ++;for( int i = 1;i <= m;i ++ ) tot[i] += tot[i - 1];for( int i = n;i;i -- ) sa[tot[x[i]] --] = i;for( int k = 1;k <= n;k <<= 1 ) {int num = 0;for( int i = n - k + 1;i <= n;i ++ ) id[++ num] = i;for( int i = 1;i <= n;i ++ ) if( sa[i] > k ) id[++ num] = sa[i] - k;memset( tot, 0, sizeof( tot ) );for( int i = 1;i <= n;i ++ ) tot[x[i]] ++;for( int i = 1;i <= m;i ++ ) tot[i] += tot[i - 1];for( int i = n;i;i -- ) sa[tot[x[id[i]]] --] = id[i];for( int i = 1;i <= n;i ++ ) rnk[i] = x[i];x[sa[1]] = num = 1;for( int i = 2;i <= n;i ++ )x[sa[i]] = ( rnk[sa[i]] == rnk[sa[i - 1]] && rnk[sa[i] + k] == rnk[sa[i - 1] + k] ) ? num : ++ num;if( n == num ) break;m = num;} }void height() {for( int i = 1;i <= n;i ++ ) rnk[sa[i]] = i;int k = 0;for( int i = 1;i <= n;i ++ ) {if( rnk[i] == 1 ) continue;if( k ) k --;int j = sa[rnk[i] - 1];while( i + k <= n && j + k <= n && s[i + k] == s[j + k] ) k ++;h[rnk[i]] = k;} }void RMQ() {for( int i = 1;i <= n;i ++ ) st[i][0] = h[i];for( int j = 1;j <= 20;j ++ )for( int i = 1;i <= n;i ++ )if( i + ( 1 << j - 1 ) > n ) break;else st[i][j] = min( st[i][j - 1], st[i + ( 1 << j - 1 )][j - 1] ); }int lcp( int l, int r ) { //左閉右閉rmqif( l > r ) return INF;int i = log( r - l + 1 ) / log( 2 );return min( st[l][i], st[r - ( 1 << i ) + 1][i] ); }void insert( int pre, int &now, int l, int r, int pos ) {if( ! now ) now = ++ cnt;if( l == r ) {t[now].sum = t[pre].sum + 1;return;}int mid = ( l + r ) >> 1;if( pos <= mid ) {t[now].r = t[pre].r;insert( t[pre].l, t[now].l, l, mid, pos );}else {t[now].l = t[pre].l;insert( t[pre].r, t[now].r, mid + 1, r, pos );}t[now].sum = t[t[now].l].sum + t[t[now].r].sum; }int query( int pre, int now, int l, int r, int L, int R ) {if( t[now].sum - t[pre].sum == 0 ) return 0;if( L <= l && r <= R ) return t[now].sum - t[pre].sum;int mid = ( l + r ) >> 1, tot = 0;if( L <= mid ) tot += query( t[pre].l, t[now].l, l, mid, L, R );if( mid < R ) tot += query( t[pre].r, t[now].r, mid + 1, r, L, R );return tot; }int main() {read( n ), read( Q );scanf( "%s", s + 1 );suffix();height();RMQ();for( int i = 1;i <= n;i ++ )insert( root[i - 1], root[i], 1, n, rnk[i] );for( int i = 1, a, b, c, d;i <= Q;i ++ ) {read( a ), read( b ), read( c ), read( d );int l = 0, r = min( b - a + 1, d - c + 1 ), ans;while( l <= r ) {//二分最后的LCP答案長度midint mid = ( l + r ) >> 1, range_l, range_r, L, R;//符合要求的子串的rank一定與c連在一起[rnk[c]-t1,rnk[c]+t2]L = 1, R = rnk[c];//二分找前綴t1 貪心越往前肯定mid越可能成立while( L <= R ) {int MID = ( L + R ) >> 1;if( lcp( MID + 1, rnk[c] ) >= mid ) range_l = MID, R = MID - 1;else L = MID + 1;}L = rnk[c], R = n;//二分找后綴t2 貪心越往后肯定mid越可能成立while( L <= R ) {int MID = ( L + R ) >> 1;if( lcp( rnk[c] + 1, MID ) >= mid ) range_r = MID, L = MID + 1;else R = MID - 1;}//只要在[a,b-mid+1]內存在任意一個即說明mid成立if( query( root[a - 1], root[b - mid + 1], 1, n, range_l, range_r ) ) ans = mid, l = mid + 1;else r = mid - 1;}print( ans ), puts( "" );}return 0; }總結
以上是生活随笔為你收集整理的[HEOI2016/TJOI2016]字符串 (后缀数组+主席树+二分)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 南航发布公告:系统异常期间已出票的机票全
- 下一篇: 中国电池厂商重要节点,数据显示宁德时代海