NOI2015字符串游戏

题目大意:
给出一个长度为N的字符串T(N<=5e5),有M次询问(M<=5e5)
每次询问有两个操作
1. 给出排名k1,k2,询问字典序排名为k1的子串的下标l,r
如果该子串和字符串T的其它子串相同,那么输出起始位置第k2小的子串的下标l,r
2. 给出子串下标l,r,问该子串在T的所有不同子串中的字典序排名k1以及该子串在所有字 符串T的子串且与其相同的子串中的起始位置排名k2

做法:
首先建立字符串T的后缀数组,算出其Height数组,Rank数组以及Sa数组
对于第一种操作,我们可以通过算所有后缀字符串的不同子串的贡献度,可以预处理出以等级i开头的不同子串数量的前缀和,通过二分可以确定出子串所在的等级和长度,然后两次二分确定出等级区间,最后通过主席树得出答案.
对于第二种操作,查询为l,r.我们先看Rank[l],即l开始的后缀的排名,然后设P=Rank[l],利用线段树区间查询求出P的左侧第一个height值小于r-l+1的值的下标Pl,再利用线段树区间查询求出P的右侧第一个height值小于r-l+1的值的下标pr.设两个下标分别为PL,PR,那么区间[PL+1,PR-1]中的后缀都有一个共同点,他们的公共前缀肯定包含子串[l,r],用第一种操作的前缀和,子串长度,height数组可得出k1,再用主席树二分进行操作得出询问的子串在与其相同的子串中的起始位置排名k2即可.
代码:

#include<iostream>
using namespace std;
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
#include<cstdlib>
const long long maxn=500010;
long long t1[maxn],t2[maxn],c[maxn];
bool cmp(long long *r,long long a,long long b,long long i){
    return r[a]==r[b]&&r[a+i]==r[b+i];
}
void da(char str[],long long sa[],long long RANKP[],long long height[],long long n,long long m){
    n++;
    long long 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=0;i<m;i++) c[i]+=c[i-1];
    for (long long i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
    for (j=1;j<=n;j<<=1){
        p=0;
        for (i=n-j;i<n;i++) y[p++]=i;
        //for (i=n-1;i>=n-j;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]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        if (p>=n) break;
        m=p;
    }
    long long k=0;
    n--;
    for (i=0;i<=n;i++) RANKP[sa[i]]=i;
    for (i=0;i<n;i++){
        if (k) k--;
        j=sa[RANKP[i]-1];
        while(str[i+k]==str[j+k]) k++;
        height[RANKP[i]]=k;
    }
}
long long RANKP[maxn],height[maxn];
long long RMQ[maxn];
long long mm[maxn];
char str[maxn];
long long sa[maxn];
int N,Q;
struct Tree{
	int a[maxn];
	int ll[maxn],rr[maxn];
	void create(int k,int l,int r){
		ll[k] = l;
		rr[k] = r;
		if(l>=r){
			a[k] = height[l+1]; 
			return;
		}
		int mid = (l+r)/2;
		create(k*2,l,mid);
		create(k*2+1,mid+1,r);
		a[k] = min(a[k*2],a[k*2+1]);
	}
	int queryl(int k,int p,int s){
	//	cout<<"\t"<<ll[k]<<" "<<rr[k]<<" "<<p<<" "<<s<<"\ta[k]="<<a[k]<<endl;
		if(a[k]>=s)
			return -1;
		if(ll[k]>=p)
			return -1;
		if(ll[k]==rr[k])
			return ll[k];
		int ans = -1;
		if(ll[k*2+1]<p&&a[k*2+1]<s)
			ans=queryl(k*2+1,p,s);
		if(ans!=-1) return ans;
		return queryl(k*2,p,s);
	}
	int queryr(int k,int p,int s){
		if(a[k]>=s)
			return -1;
		if(rr[k]<p)
			return -1;
		if(ll[k]==rr[k])
			return ll[k];
		int ans = -1;
		if(rr[k*2]>=p&&a[k*2]<s)
			ans=queryr(k*2,p,s);
		if(ans!=-1) return ans;
		return queryr(k*2+1,p,s);
	}
};
Tree sdt;
int a[maxn];
struct Data{
	int l,r,sum;
};
Data T[maxn*15];
int cnt = 0;//现在线段树中一共构建了几个节点
vector<int> v;
int get(int x)//获取x的离散化之后的值
{
	return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
void insert(int l,int r,int &now,int pre,int x){
	++cnt;now = cnt;
	T[now] = T[pre];//当前线段树由前一个线段树得到
	T[now].sum += 1;
	if(l==r)	return;
	int mid = (l+r)/2;
	if(mid>=x)
		insert(l,mid,T[now].l,T[pre].l,x);
	else
		insert(mid+1,r,T[now].r,T[pre].r,x);			
}
int query(int l,int r,int x,int y,int k){
	if(l==r)
		return l;
	int mid = (l+r)/2;
	int R_sum = T[T[y].r].sum - T[T[x].r].sum;
	if(k<=R_sum)
		query(mid+1,r,T[x].r,T[y].r,k);
	else
		query(l,mid,T[x].l,T[y].l,k-R_sum);
}
int root[maxn],ass[maxn];
pair<int,int> getpos(int k1,int n){
    int l=1,r=n,mid;//二分等级区间
    while(l<r){
        mid=(l+r)>>1;
        if(ass[mid]<k1) l=mid+1;
        else r=mid;
    }
    return make_pair(l,k1-ass[l-1]+height[l]);
}
void query1(int k1,int k2,int n){
    pair<int,int> pp=getpos(k1,n);
    int p=pp.first;
    int len=pp.second;
	int pl = sdt.queryl(1,p,len);
	int pr = sdt.queryr(1,p,len);
	if(pl==-1) pl = 1;else pl += 1;
	if(pr==-1) pr = n;
	if(pr>n) pr = n;if(pl>n) pl = n;
	if(pr-pl+1<k2){
	    printf("%d %d\n",sa[p],sa[p]+len-1);
	    return;
	}
	k2=pr-pl+2-k2;
	int res=query(1,v.size(),root[pl-1],root[pr],k2);
	printf("%d %d\n",v[res-1],v[res-1]+len-1);
}
int main(){
    scanf("%s",str); 
	scanf("%d",&Q);
    int n=strlen(str);
    str[n]=0;
    da(str,sa,RANKP,height,n,200);
    for(int i=1;i<=n;i++) ++sa[i];
    for(int i=1;i<=n;i++) 
		ass[i]=ass[i-1]+(n-sa[i]+1)-height[i];
    for(int i=1;i<=n;i++)
		v.push_back(sa[i]);
	sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());
	for(int i=0;i<n;i++)
		insert(1,v.size(),root[i+1],root[i],get(sa[i+1]));
    height[n+1] = height[n];
    sdt.create(1,1,n);
    for(int i=0;i<Q;i++){
    	int p,k1,k2;
    	scanf("%d%d%d",&p,&k1,&k2);
    	if(p==1){
    		query1(k1,k2,n);
		}
		else{
			int l = k1,r = k2;
    		int len = r-l+1;
    		int p = RANKP[l-1];
    		int pl = sdt.queryl(1,p,len);
    		int pr = sdt.queryr(1,p,len);
    		if(pl==-1) pl = 1;else pl += 1;
    		if(pr==-1) pr = n;
    		if(pr>n) pr = n;if(pl>n) pl = n;
    		long long ans1 = ass[pl-1]+len-height[pl];
    		int ll = 1,rr = pr-pl+1;
    		long long ans2 = -1;
    		while(ll<=rr){
    			int mid=(ll+rr)/2;
    			int ans = query(1,v.size(),root[pl-1],root[pr],pr-pl+2-mid);
    			if(v[ans-1]==l){
    				ans2 = mid;break;
				}else if(v[ans-1]>l)
					rr = mid - 1;
				else
					ll = mid + 1;
			}
			printf("%lld %lld\n",ans1,ans2);
		}
	}
    return 0;
}

  

posted @ 2018-12-16 21:26  UUUUh  阅读(248)  评论(0编辑  收藏  举报