2021 CCPC 桂林 J 后缀数组+扫描线

发布时间 2023-10-27 16:36:13作者: 种树鼠鼠

原题链接

设字符串长度为n,下标从1开始。后缀数组进行排序后,对每个后缀i,它能贡献的不同的字串就是该后缀从\(ht_i+1\)到结尾\(n-sa_i+1\)的所有前缀,利用差分和前缀和预处理出长度小于等于i的子串贡献出的不同字串\(sum_i\),对每次查询k,可以二分找到它所在长度\(len_k\)和它在所在的长度组中顺序,即\(k-sum_{len_k-1}\)

可以用扫描线的思想,以长度\(len\)为时间轴,将所有询问k离线按照所在长度组\(len\)排序。用一个树状数组维护1到n的所有后缀(按字典序排序的后缀,在当前的长度\(len\)产生的贡献,每个后缀产生的贡献在\([ht_i+1,n-sa_i+2)\),可以在遍历到这两个端点上时让对应的位置+1或-1。因为在某一长度下,每个后缀产生的贡献为0或者1,而树状数组维护的是前缀和,对当前长度组的每个查询k,可以在树状数组上二分,找到它所在组中的顺序\(k-sum_{len_k-1}\)的位置,即\([1,n]\)中的第\(k-sum_{len_k-1}\)个1的位置,记为j,那么对应到原串就是\(sa_j,sa_j+len-1\)。但到这还没完,题目要求找到第k大的串出现的最左边的位置,因此我们还要在对后缀数组二分,具体来说,对位置j,先二分找到最长的区间\([L,R]\),使得\(LCP(s[sa_i\dots n],s[sa_j\dots n])\ge len,i\in [L,R]\),然后在用st表查询出\([L,R]\)\(sa\)的最小值p,,答案即为p,p+len-1

#include<bits/stdc++.h>
#define x first
#define y second
#define mp make_pair
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=1e6+10,M=22;
int m,n;
char s[N];
int sa[N],rk[N],ht[N],RMQ[M][N],lg[N],MIN[M][N],x[N],y[N],c[N];
ll sum[N];
vector<pii>evt[N];
vector<pii>que[N];
void get_sa(int n,int m=255)//m为字符集大小
{
   for (int i = 1; i <= n; i ++ ) c[x[i] = s[i]] ++ ;
    for (int i = 2; i <= m; i ++ ) c[i] += c[i - 1];
    for (int i = n; i; i -- ) sa[c[x[i]] -- ] = i;//初始化,所有后缀按第一个字母排序
    for (int k = 1; k <= n; k <<= 1)
    {
        int num = 0;
        for (int i = n - k + 1; i <= n; i ++ ) y[ ++ num] = i;
        for (int i = 1; i <= n; i ++ )
            if (sa[i] > k) y[ ++ num] = sa[i] - k;//按第二关键字排序
        for (int i = 1; i <= m; i ++ ) c[i] = 0;
        for (int i = 1; i <= n; i ++ ) c[x[i]] ++ ;
        for (int i = 2; i <= m; i ++ ) c[i] += c[i - 1];
        for (int i = n; i; i -- ) sa[c[x[y[i]]] -- ] = y[i], y[i] = 0;//按第一关键字排序
        swap(x, y);
        x[sa[1]] = 1, num = 1;
        for (int i = 2; i <= n; i ++ )//将有序的序列离散化
  x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) ? num : ++ num;
        if (num == n) break;//所有后缀顺序各不相同
        m = num;
    }
}
void get_ht(int n){
    for(int i=1;i<=n;i++) rk[sa[i]]=i;
    for(int i=1,k=0;i<=n;i++){
         if(rk[i]==1) continue;
         k=max(k-1,0);
         int j=sa[rk[i]-1];
         while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k]) ++k;
        ht[rk[i]]=k;
    }
    ht[1]=0;
    for(int i=2;i<N;i++) lg[i]=lg[i/2]+1;
    for(int i=1;i<=n;i++) RMQ[0][i]=ht[i],MIN[0][i]=sa[i];
    for(int i=1;i<M;i++)
      for(int j=1;j+(1<<i)-1<=n;j++)
     RMQ[i][j]=min(RMQ[i-1][j],RMQ[i-1][j+(1<<i-1)]),
     MIN[i][j]=min(MIN[i-1][j],MIN[i-1][j+(1<<i-1)]);
}
int LCP(int L,int R){
    L++;int k=lg[R-L+1];
    return min(RMQ[k][L],RMQ[k][R-(1<<k)+1]);
}
int Min(int L,int R){
    int k=lg[R-L+1];
    return min(MIN[k][L],MIN[k][R-(1<<k)+1]);
}
template<typename T>
struct Fenwick{
    int n;
    vector<T> tr;
    Fenwick(int n) : n(n), tr(n + 1, T()){}
    int lowbit(int x){
        return x & -x;
    }
    void modify(int x, T c){
        for(int i = x; i <= n; i += lowbit(i)) tr[i] += c;
    }
 
    T query(int x){
        T res = T();
        for(int i = x; i; i -= lowbit(i)) res += tr[i];
        return res;
    }
    int find(ll p){ //返回前缀和等于p的最小的数
       int pos=0;
       if(query(n)<p) return -1;
       T t=T();
        for(int j=M-1;j>=0;j--){
             if(pos+(1<<j)<=n&&t+tr[pos+(1<<j)]<p)
             {
                pos+=1<<j;
                t+=tr[pos];
             }
        }
        return pos+1;
    }
};
using BIT = Fenwick<ll>;
void solve(){
    cin>>s+1;
     n=strlen(s+1);
     get_sa(n);
     get_ht(n);
    int Q;
    cin>>Q;
    BIT tr(n);

  for(int i=1;i<=n;i++){
        if(ht[i]<n-sa[i]+1){
         sum[ht[i]+1]++,sum[n-sa[i]+2]--;
         evt[ht[i]+1].push_back({i,1});
         evt[n-sa[i]+2].push_back({i,-1});
        }
    }
    for(int i=1;i<=n;i++) sum[i]+=sum[i-1];
    for(int i=1;i<=n;i++) sum[i]+=sum[i-1]; 

   
    for(int i=1;i<=Q;i++){
        ll k;
        cin>>k;
        int l=0,r=n;
        while(l<r){
             int mid=l+r+1>>1;
             if(sum[mid]<k) l=mid;
             else r=mid-1;
        }
         k-=sum[l];
        que[l+1].push_back({k,i});
    }


    vector<pii>ans(Q+1);
    for(int i=1;i<=n;i++){

        for(auto [x,y]:evt[i]){
             tr.modify(x,y);
           }
           for(auto [x,id]:que[i]){
             int t=tr.find(x);
              if(t==-1) ans[id]={-1,-1}; 
              else {
                 int ql=t,qr=t;
                  if(t>1&&LCP(t-1,t)>=i){
                   int l=1,r=t-1;
                     while(l<r){
                         int mid=l+r>>1;
                         if(LCP(mid,t)>=i) r=mid;
                         else l=mid+1;
                     }
                     ql=r;
                  }
                  if(t<n&&LCP(t,t+1)>=i){
                      int l=t+1,r=n;
                     while(l<r){
                         int mid=l+r+1>>1;
                         if(LCP(t,mid)>=i) l=mid;
                         else r=mid-1;
                     }
                     qr=r;
                  }
               int tem=Min(ql,qr);
               ans[id]={tem,tem+i-1};       
           } 
    }
    }
    for(int i=1;i<=Q;i++){
        if(ans[i].x==0) ans[i]={-1,-1};
        cout<<ans[i].x<<" "<<ans[i].y<<"\n";
    }
} 
int main()
{
      ios::sync_with_stdio(false);
      cin.tie(0),cout.tie(0);
      int T=1; 
      while(T--) solve();    
    return 0;
}