hdu5008 Boring String Problem 后缀数组+二分+RMQ

      题意大概就是给一个串,每次查询q,令k=(q^l^r)+1,其中^表示异或,l,r是上次查询的答案,初始为0,去串中字典序第k小的子串最靠左出现的位置。之前做过一题 SPOJ 的SUBLEX基本和这题一样,不过那题只要找到第k小的字符串就行了,这题需要找到这个子串最靠右出现的位置。考虑找到第k小的子串,直接拿原串先构造后缀数组,统计一下第i个后缀有多少个不同的前缀num[i](也就是在原串中有多少个不重复的子串),按sa排序后,这些连续出现的子串的字典序也是相同的,那么对num[i]求前缀和后就可以去二分一个位置,找到字典序第k小的子串出现的位置了。这里找到的位置不一定是最靠左的,所以还要在原串中找一下最左的位置,其实到了这里,直接两个指针,一个向前一个向后,暴力找到最长的连续的height[i]>=目标子串长度 的位置下标L,R,然后RMQ(L,R,sa)找到最小的位置就好了,当然极限数组(例如10W个a)肯定会TLE的,但它居然能过...而且还更快一点,果然是数据太弱了- =...当然,本着严(zi)谨(nue)的态度,好孩子不要学这种方法。一种可行的做法,确定了当前的位置pos,我们要做的就是在pos后面找个R,前面找个L使得[L,R]这个区间的height的最小值>=目标子串的长度,那么可以直接在[POS,n]中二分处位置R,同样在[1,POS]中二分出L,由于RMQ预处理后查询是O(1),所以两边logN的二分就可以找到答案的区间[L,R],最后再RMQ一下就得到最后的答案了。注意这里求区间的RMQ和求答案的RMQ是查询的两个数组,要分别初始化...

/*=============================================================================
#  Author:Erich
#  FileName:
=============================================================================*/
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <queue>
#include <stack>
#define lson id<<1,l,m
#define rson id<<1|1,m+1,r

using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const ll INF=1ll<<60;
const double PI=acos(-1.0);
const int maxn=205000;
const int mod=1e9+7;
char ss[maxn];
int belong[maxn];
int s[maxn],rs[maxn];
int sa[maxn],t[maxn],t2[maxn],c[maxn];
int n,m,tt;
int rank[maxn],height[maxn];
int len,l;
inline int idx(char c)
{
	return c-'a'+1;
}
inline char fdx(int x)
{
	return char(x-1+'a');
}
void getheight(int n)
{
    int i,j,k=0;
    for (i=0; i<=n; i++) rank[sa[i]]=i;

    for (i=0; i<n; i++)
    {
        if (k) k--;
        int j=sa[rank[i]-1];
        while(s[i+k]==s[j+k]) k++;
        height[rank[i]]=k;
    }
}
void build_ss(int m,int n)
{
    n++;
    int i,*x=t,*y=t2;
    for (int i=0; i<m; i++) c[i]=0;
    for (int i=0; i<n; i++) c[x[i]=s[i]]++;
    for (int i=1; i<m; i++) c[i]+=c[i-1];
    for (int i=n-1; i>=0; i--)
      sa[--c[x[i]]]=i;
    for (int k=1; k<=n; k<<=1)
    {
        int p=0;
        for (i=n-k; i<n; i++) y[p++]=i;
        for (i=0; i<n; i++) if (sa[i]>=k) y[p++]=sa[i]-k;

        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]+k]==y[sa[i]+k])? p-1 : p++;
        if (p>=n) break;
        m=p;
    }
}
int d[2][maxn][50];
int LOG[maxn];
int RMQ_init(int x,int A[])
{
	for(int i=1; i<=n; i++) d[x][i][0]=A[i];
	for (int j=1; (1<<j)<=n; j++)
		for (int i=1; i+(1<<j)-1<=n; i++)
			d[x][i][j]=min(d[x][i][j-1],d[x][i+(1<<(j-1))][j-1]);
}
int RMQ(int x,int L,int R)
{
	int k=LOG[R-L+1];
	return min(d[x][L][k],d[x][R-(1<<k)+1][k]);
}
ll num[maxn];
int main()
{
//    freopen("in.txt","r",stdin);
    int k=0;
    for (int i=0; i<105000; i++)
    {
        while((1<<(k+1))<=i) k++;
        LOG[i]=k;
    }
	while(~scanf("%s",ss))
	{
		int l=strlen(ss);
		for(int i=0; i<l; i++)
			s[i]=idx(ss[i]);
		n=l;
		s[n]=0;
		build_ss(33,n);
		getheight(n);

		for (int i=0; i<=n; i++)
			num[i]=n-sa[i];
		for (int i=1; i<=n; i++)
			num[i]-=height[i];
		for (int i=1; i<=n; i++)
			num[i]+=num[i-1];
		ll tot=num[n];
		scanf("%d",&m);
		ll la=0,lb=0;
		ll k;
		RMQ_init(0,height);
		RMQ_init(1,sa);
		while(m--)
		{
			scanf("%I64d",&k);
			k^=la;
			k^=lb;
			k++;
			if (k>=1 && k<=tot)
			{
				int pos=lower_bound(num+1,num+1+n,k)-num;
				int len=k-num[pos-1]+height[pos];
				int l=pos+1,r=n;
				int mid;
				int L,R;
				while(l<r)
				{
				    mid=(l+r)>>1;
				    if (RMQ(0,pos+1,mid)>=len) l=mid+1;
				    else r=mid;
				}
				if (RMQ(0,pos+1,l)>=len) R=l;
				else R=l-1;

				l=1; r=pos;
				while(l<r)
				{
				    mid=(l+r)>>1;
				    if (RMQ(0,mid,pos)>=len) r=mid;
				    else l=mid+1;
				}
				if (RMQ(0,l,pos)>=len) L=l-1;
				else L=l;


				la=RMQ(1,L,R);
				lb=la+len-1;
				la++;
				lb++;
				printf("%I64d %I64d\n",la,lb);
			}
			else
			{
				la=lb=0;
				puts("0 0");
			}
		}
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值