主席树 + 后缀数组求LCP + 二分套二分 ---- P4094 [HEOI2016/TJOI2016]字符串

博客介绍了如何利用后缀数组和主席树解决一个字符串中两个子串的最大公共前后缀(Longest Common Prefix, LCP)问题。通过二分查找和后缀排序的性质,将原问题转换为判断性问题,再利用主席树进行区间查询,高效地找到符合条件的LCP长度。代码展示了具体的实现过程。
摘要由CSDN通过智能技术生成

题目链接


题目大意:

在这里插入图片描述


解题思路:

设我们的答案为 m i d mid mid(注意这里有坑是 [ a , b ] [a,b] [a,b]的所有子串和 [ c , d ] [c,d] [c,d]这个子串的最长 l c p lcp lcp),那么我们会发现一个很有趣的事实: 如果 m i d mid mid可行的话,那么任意一个比 m i d mid mid小的数也可行

也就是说,问题满足可二分性,那么我们可以二分答案,将原问题转化为一个判定性问题: m i d mid mid这个答案行不行?

那么我们发现,如果 m i d mid mid这个答案可以的话,就会存在一个后缀 S S S

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)>=mid lcp(S,c)>=mid

那么这里面有两个限制最好处理的是第二个因为我们直知道对于一个后缀 c c c和它 l c p lcp lcp越大的后缀在后缀排序中的 r a n k rank rank是很接近的,那意思就是说如果把这些后缀排好序,那么lcp符合要求的一定是一段连续的区间,(为什么?,因为我们发现排好序以后, l c p lcp lcp这个函数是单峰的,并且峰值在自己这里)

那么我们可以二分出一个 r a n k c ∈ [ r a n k l , r a n k r ] rank_c\in[rank_l,rank_r] rankc[rankl,rankr]里面所以的后缀和 c c c l c p lcp lcp都是大于等于 m i d mid mid

然后看看 [ r a n k l , r a n k r ] [rank_l,rank_r] [rankl,rankr]里面有没有 [ a , b − m i d + 1 ] [a,b-mid+1] [a,bmid+1]那就是用 s a sa sa数组了
s a [ r a n k [ i ] ] = i sa[rank[i]] = i sa[rank[i]]=i
动他区间查询就是主席树啦!!
用rank建主席树
就好了


AC code

#include <bits/stdc++.h>
#define mid ((l + r) >> 1) 
#define Mid ((l + r + 1) >> 1) 
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
#define ms(a,al) memset(a,al,sizeof(a))
#define log2(a) log(a)/log(2)
#define _for(i,a,b) for( int i = (a); i < (b); ++i)
#define _rep(i,a,b) for( int i = (a); i <= (b); ++i)
#define for_(i,a,b) for( int i = (a); i >= (b); -- i)
#define rep_(i,a,b) for( int i = (a); i > (b); -- i)
#define lowbit(x) ((-x) & x)
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define INF 0x3f3f3f3f
#define LLF 0x3f3f3f3f3f3f3f3f
#define hash Hash
#define next Next
#define pb push_back
#define f first
#define s second
using namespace std;
const int N = 2e6 + 10, mod = 1e9 + 9;
const int maxn = 500010;
const long double eps = 1e-5;
const int EPS = 500 * 500;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
typedef pair<double,double> PDD;
template<typename T> void read(T &x) {
    x = 0;char ch = getchar();ll f = 1;
    while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
    while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
template<typename T, typename... Args> void read(T &first, Args& ... args)  {
    read(first);
    read(args...);
}
//............................. 
int n, m, len;
int a[maxn];
char s[maxn]; 
//......................
// sa[l]排序是lth的后缀的开始位置
//ra[l]是起点是l的后缀排名多少
//lcp(suf(i),suf(j)) = min(H(ra[i] + 1),....H(ra[j]));
//区间最小值倍增求
// H(i)是rk[i]和re[i-1]的lcp
//这个板子下标是从0开始,rank从1开始
//传进来的是int数组但是不能有0 
struct SA {
    int sa[maxn], ra[maxn], height[maxn];
    int t1[maxn], t2[maxn], c[maxn];
    void build(int *str, int n, int m) {//字符数组,长度,字符种类 
        str[n] = 0;
        n++;
        int i, j, p, *x = t1, *y = t2;
        for (i = 0; i < m; i++) c[i] = 0;//排序的桶
        for (i = 0; i < n; i++) c[x[i] = str[i]]++;
        for (i = 1; i < m; i++) c[i] += c[i - 1];
        for (i = n - 1; i >= 0; i--) sa[--c[x[i]]] = i;
        for (j = 1; j <= n; j <<= 1) {//所有长度为1,2,4,8....的子串的排序
       //长度够长的话就是所有的后缀排序
            p = 0;
            for (i = n - j; i < n; i++) y[p++] = i;
            for (i = 0; i < n; i++) if (sa[i] >= j) y[p++] = sa[i] - j;
            for (i = 0; i < m; i++) c[i] = 0;
            for (i = 0; i < n; i++) c[x[y[i]]]++;
            for (i = 1; i < m; i++) c[i] += c[i - 1];
            for (i = n - 1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];
            swap(x, y);
            p = 1;
            x[sa[0]] = 0;
            for (i = 1; i < n; i++)
                x[sa[i]] = (y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + j] == y[sa[i] + j]) ? p - 1 : p++;
            if (p >= n) break;
            m = p;
        }
        n--;
        for (int i = 0; i <= n; i++) ra[sa[i]] = i;
        for (int i = 0, j = 0, k = 0; i <= n; i++) {
            if (k) k--;
            j = sa[ra[i] - 1];
            while (str[i + k] == str[j + k]) k++;
            height[ra[i]] = k;
        }
        st_init(height, n);
    }
    int lg[maxn], table[23][maxn];
    void st_init(int *arr, int n) {
        if (!lg[0]) {
            lg[0] = -1;
            for (int i = 1; i < maxn; i++)
                lg[i] = lg[i / 2] + 1;
        }
        for (int i = 1; i <= n; ++i)
            table[0][i] = arr[i];
        for (int i = 1; i <= lg[n]; ++i)
            for (int j = 1; j <= n; ++j)
                if (j + (1 << i) - 1 <= n)
                    table[i][j] = min(table[i - 1][j], table[i - 1][j + (1 << (i - 1))]);
                    //从j开始长度是2^i次方得st表
    }
    int lcp(int l, int r) {
        l = ra[l], r = ra[r];
        if (l > r) swap(l, r);
        ++l;
        int t = lg[r - l + 1];//区间长度
        return min(table[t][l], table[t][r - (1 << t) + 1]);
    }
} sa;

//.............................
struct node {
	int lson, rson;
	int val;
} tr[maxn * 40];
int root[maxn], cnt;
void insert(int &now, int pre, int l, int r, int pos) {
	now = ++ cnt;
	tr[now] = tr[pre];
	tr[now].val += 1;
	if(l == r) return;
	if(pos <= mid) insert(tr[now].lson,tr[pre].lson,l,mid,pos);
	else insert(tr[now].rson,tr[pre].rson,mid+1,r,pos);
}
int ask(int ltree, int rtree, int l, int r, int posl, int posr) {
	if(posl <= l && posr >= r) return tr[rtree].val - tr[ltree].val;
	int ans = 0;
	if(posl <= mid) ans += ask(tr[ltree].lson,tr[rtree].lson,l,mid,posl,posr);
	if(posr > mid) ans += ask(tr[ltree].rson,tr[rtree].rson,mid+1,r,posl,posr);
	return ans; 
}
//.............................

bool check(int ans, int a, int b, int c, int d) {
	int up, down;
	int l = 1, r = sa.ra[c];// 二分下界
	while(l < r) {
		if(sa.lcp(sa.sa[mid],c) < ans) l = mid + 1;
		else r = mid;
	} 
	down = l;
	l = sa.ra[c], r = n;
	while(l < r) {
		if(sa.lcp(sa.sa[Mid],c) < ans) r = Mid - 1;
		else l = Mid;
	}
	up = l;
	return (ask(root[down-1],root[up],1,len,a+1,b-ans+2) > 0);
}


int main() {
   scanf("%d%d",&n,&m);
   scanf("%s",s);
   len = strlen(s);
   for(int i = 0; i < len; ++ i) a[i] = (int)s[i];
   sa.build(a,n,225);
   for(int i = 1; i <= len; ++ i) //枚举rank = i
      insert(root[i],root[i-1],1,len,sa.sa[i]+1);
   while(m --) {
   	  int a, b, c, d;
   	  scanf("%d%d%d%d",&a,&b,&c,&d);
   	  a --, b --, c --, d --;
   	  int l = 0, r = min(b-a+1,d-c+1);
   	  while(l < r) {
   	  	if(check(Mid,a,b,c,d)) l = Mid;
	    else r = Mid - 1;
	  }
	  printf("%d\n",l);
   }  
   return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值