[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 4

	

1
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
二分。。。
如果一个长度 m i d mid mid可行,那么比 m i d mid mid小的所有长度也都可行
即决策具有单调性,可以二分


Step2
如果二分的长度 m i d mid mid可行,则这个串必然满足

  1. 开头端点在 [ a , b − m i d + 1 ] [a,b-mid+1] [a,bmid+1]
  2. L C P ( s , c ) ≥ m i d LCP(s,c)\ge mid LCP(s,c)mid

Step3
最长公共前缀,无脑上后缀数组,用 R M Q RMQ RMQ查询 L C P LCP LCP


Step4
步骤二转化为二元限制问题
解决方案:摁死一元, l o g log log另一元
将后缀数组的 r a n k rank rank排序后建立主席树,查找区间 [ a , b − m i d + 1 ] [a,b-mid+1] [a,bmid+1]
r a n k rank rank建立主席树是由于其 L C P LCP LCP的特殊性质决定的
L C P LCP LCP满足条件的一定是一个连续区间,再套用二分,二分出区间的左右端点


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 ) {
    
     //左闭右闭rmq
	if( 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答案长度mid
			int 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;
} 

猜你喜欢

转载自blog.csdn.net/Emm_Titan/article/details/113899203