HDU 6704 K-th occurrence 2019CCPC网络赛 (后缀数组+ST表+二分+主席树)

it2026-02-26  8

题面描述

You are given a string S consisting of only lowercase english letters and some queries.

For each query ( l , r , k ) (l,r,k) (l,r,k), please output the starting position of the k − t h k-th kth occurence of the substring S l S l + 1 . . . S r S_lS_{l+1}...S_r SlSl+1...Sr in S S S.

输入格式

The first line contains an integer T ( 1 ≤ T ≤ 20 ) T(1≤T≤20) T(1T20), denoting the number of test cases. The first line of each test case contains two integer N ( 1 ≤ N ≤ 1 0 5 ) , Q ( 1 ≤ Q ≤ 1 0 5 ) N(1≤N≤10^5),Q(1≤Q≤10^5) N(1N105),Q(1Q105), denoting the length of S S S and the number of queries. The second line of each test case contains a string S ( ∣ S ∣ = N ) S(|S|=N) S(S=N) consisting of only lowercase english letters. Then Q lines follow, each line contains three integer l , r ( 1 ≤ l ≤ r ≤ N ) l,r(1≤l≤r≤N) l,r(1lrN) and k ( 1 ≤ k ≤ N ) k(1≤k≤N) k(1kN), denoting a query. There are at most 5 5 5 testcases which N N N is greater than 1 0 3 10^3 103.

输入样例

2 12 6 aaabaabaaaab 3 3 4 2 3 2 7 8 3 3 4 2 1 4 2 8 12 1 1 1 a 1 1 1

输出样例

5 2 -1 6 9 8 1

题面分析

给一个长度为 n n n的串,有 q q q次询问,每次给一个三元组 ( l , r , k ) (l,r,k) (l,r,k)求S[l,r]出现第k次的位置。 因为 n n n很大,对于某个串出现多次的位置判断等可以用后缀数组处理,如果 S [ l , n ] S[l,n] S[l,n]和其他任意一个串 q [ x , n ] q[x,n] q[x,n]的LCP ≥ \geq r-l+1,则在 q q q中S[l,r]出现过。 因为 q q q是有序的,所以我们需要找到符合题意的 q q q的范围。 注意到Rank[i]的后缀与其他后缀的LCP随着排名之差绝对值增大而减小,因此我们考虑二分Rank得到满足条件的 q q q的Rank上下界 a n s L , a n s R ansL,ansR ansL,ansR。 于是接下来问题变成了如何第k次的下标,下标位置就是SA数组,考虑根据SA数组建立一颗可持久化线段树,然后每次询问 [ a n s L , a n s R ] [ansL,ansR] [ansL,ansR]内的第k个即可。

AC代码(1400ms)

#include <bits/stdc++.h> using namespace std; const int maxn=3e5; #define ll long long namespace SA { int SA[maxn+10];//所有后缀排序后第i小的后缀的起始位置的下标; int Rank[maxn+10];//Rank[i] 第i个后缀的排名,SA[Rank[i]]=Rank[SA[i]]=i int Height[maxn+10]; //Height[i]=排名为i的后缀与排名为(i-1)的后缀的最长公共前缀 //Height[i]>=Height[i-1]+1 //LCP(i,k)=min(LCP(j,j-1)) (1<i<=j<=k<=n) //不同子串的个数:n(n+1)/2-(Height[2]+Height[3]+...+Height[n]) int tax[maxn+10], tp[maxn+10]; //tax[i] 计数排序辅助数组; //tp[i] rk的辅助数组(计数排序中的第二关键字),与SA意义一样。 int len; int s[maxn+10]; int m;//m当前离散后的排名种类数 void RSort() { //rk第一关键字,tp第二关键字 for(int i=0; i<=m; i++) tax[i]=0; for(int i=1; i<=len; i++) tax[Rank[tp[i]]]++; for(int i=1; i<=m; i++) tax[i]+=tax[i-1]; for(int i=len; i>=1; i--) SA[tax[Rank[tp[i]]]--]=tp[i]; //确保满足第一关键字的同时,再满足第二关键字的要求 }//计数排序,把新的二元组排序 int cmp(int *f, int x, int y, int w) { return f[x]==f[y] && f[x+w]==f[y+w]; } //通过二元组两个下标的比较,确定两个子串是否相同 void GetSA() { for(int i=1; i<=len; i++) { Rank[i]=s[i], tp[i]=i; } m=200; RSort(); for(int w=1, p=1, i; p<len; w+=w, m=p) { //把子串长度翻倍,更新rk //w 当前一个子串的长度; //当前的tp(第二关键字)可直接由上一次的SA的得到 for(p=0, i=len-w+1; i<=len; i++) tp[++p]=i; //长度越界,第二关键字为0 for(i=1; i<=len; i++) if (SA[i]>w) tp[++p]=SA[i]-w; //更新SA值,并用tp暂时存下上一轮的rk(用于cmp比较) RSort(), swap(Rank, tp), Rank[SA[1]]=p=1; //用已经完成的SA来更新与它互逆的rk,并离散rk for(i=2; i<=len; i++) Rank[SA[i]]=cmp(tp, SA[i], SA[i-1], w) ? p : ++p; } //离散:把相等的字符串的rk设为相同。 } void GetHeight() { //LCP int j, k=0; for(int i=1; i<=len; Height[Rank[i++]]=k) for(k=k ? k-1 : k, j=SA[Rank[i]-1]; s[i+k]==s[j+k]; ++k); //这个知道原理后就比较好理解程序 } void PrintSA() { for(int i=1; i<=len; i++) { printf("%d ", SA[i]); } printf("\n"); } void PrintHeight() { for(int i=1; i<=len; i++) { printf("%d ", Height[i]); } printf("\n"); } void PrintRank() { for(int i=1; i<=len; i++) { printf("%d ", Rank[i]); } printf("\n"); } } namespace ST { const int logn=30; int f[maxn+10][logn], Log[maxn+10]; void init_log() { Log[0]=-1; Log[1]=0; Log[2]=1; for(int i=3; i<maxn; i++) { Log[i]=Log[i/2]+1; } } void build(int n, int *a) { for(int i=1; i<=n; i++) { f[i][0]=a[i]; } init_log(); for(int j=1; j<=logn; j++) { for(int i=1; i+(1<<j)-1<=n; i++) { f[i][j]=min(f[i][j-1], f[i+(1<<(j-1))][j-1]); } } } int query(int l, int r) { if (l==r) return SA::len-SA::SA[l]; if (l>r) swap(l, r); l++;//这里+1是因为LCP定义!!! int s=Log[r-l+1]; return min(f[l][s], f[r-(1<<s)+1][s]); } } namespace President_Tree { int cnt=0; //cnt:总节点个数 const int Mul=40; int T[maxn+10]; //T:前缀和,T[i]就是1~i中第i颗的根节点 int sum[maxn*Mul+10], L[maxn*Mul+10], R[maxn*Mul+10]; //sum:统计sum[i]对应区间中值域[l',r']个数 //L,R:L[i]指权值线段树中编号为i的左右孩子编号 inline int President_Tree_init() { cnt=0; memset(L, 0, sizeof(L)); memset(R, 0, sizeof(R)); memset(sum, 0, sizeof(sum)); memset(T, 0, sizeof(T)); } inline int build(int l, int r)//和线段树类似 { int rt=++cnt; sum[rt]=0; if (l<r)//这里不执行的时候就是叶子节点 { int mid=(l+r)>>1; L[rt]=build(l, mid); R[rt]=build(mid+1, r); } return rt; } inline int update(int pre, int l, int r, int x) { int rt=++cnt;//开辟新节点 L[rt]=L[pre]; R[rt]=R[pre];// sum[rt]=sum[pre]+1;//更新个数 if (l<r) { int mid=(l+r)>>1; if (x<=mid) L[rt]=update(L[pre], l, mid, x); else R[rt]=update(R[pre], mid+1, r, x); //不断二分开辟新节点,直到对应的点被建立 } return rt;//返回的是上一个被建立的点 } inline int query(int u, int v, int l, int r, int k) //第k大的数,当前左右儿子节点、当前区间、第k大 { if (l>=r) return l; int x=sum[L[v]]-sum[L[u]];//处理左边区间数字个数 int mid=(l+r)>>1; if (x>=k)//左边的区间个数是否到了k return query(L[u], L[v], l, mid, k); return query(R[u], R[v], mid+1, r, k-x); } } char a[maxn+10]; int b[maxn+10]; void solve() { int t; scanf("%d", &t); while(t--) { int len, q; scanf("%d%d", &len, &q); scanf("%s", a+1); for(int i=1; i<=len; i++) { SA::s[i]=a[i]-'a'+1; } SA::len=len; SA::s[len+1]=0; SA::GetSA(); SA::GetHeight(); ST::build(len, SA::Height); for(int i=1; i<=SA::len; i++) { b[i]=SA::SA[i]; } sort(b+1, b+SA::len+1); int cntb=unique(b+1, b+SA::len+1)-(b+1); President_Tree::T[0]=President_Tree::build(1, cntb); for(int i=1; i<=SA::len; i++) { int index=lower_bound(b+1, b+SA::len+1, SA::SA[i])-b; President_Tree::T[i]=President_Tree::update(President_Tree::T[i-1], 1, cntb, index); } while(q--) { int l, r, k; scanf("%d%d%d", &l, &r, &k); int L=1, R=SA::Rank[l]-1; int ansL=SA::Rank[l], ansR=SA::Rank[l]; while(L<=R) { // printf("L=%d R=%d\n", L, R); int mid=(L+R)>>1; if (ST::query(mid, SA::Rank[l])>=r-l+1) { ansL=mid; R=mid-1; } else { L=mid+1; } } L=SA::Rank[l]+1, R=len; while(L<=R) { // printf("L=%d R=%d\n", L, R); int mid=(L+R)>>1; if (ST::query(SA::Rank[l], mid)>=r-l+1) { ansR=mid; L=mid+1; } else { R=mid-1; } } // printf("ansL=%d ansR=%d\n", ansL, ansR); if (ansR-ansL+1<k) { printf("-1\n"); continue; } printf("%d\n", President_Tree::query(President_Tree::T[ansL-1], President_Tree::T[R], 1, cntb, k)); } } } int main() { // ios_base::sync_with_stdio(false); // cin.tie(0); // cout.tie(0); #ifdef ACM_LOCAL freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); long long test_index_for_debug=1; char acm_local_for_debug; while(cin>>acm_local_for_debug) { cin.putback(acm_local_for_debug); if (test_index_for_debug>100) { throw runtime_error("Check the stdin!!!"); } auto start_clock_for_debug=clock(); solve(); auto end_clock_for_debug=clock(); cout<<"\nTest "<<test_index_for_debug<<" successful"<<endl; cerr<<"Test "<<test_index_for_debug++<<" Run Time: " <<double(end_clock_for_debug-start_clock_for_debug)/CLOCKS_PER_SEC<<"s"<<endl; cout<<"--------------------------------------------------"<<endl; } #else solve(); #endif return 0; }

后记

综合性很强的题,特此记录。 DrGilbert 2020.10.22

最新回复(0)