K-th occurrence(后缀树组+划分树+ST表+RMQ+二分)

2019CCPC网络选拔赛1003 HDU6704

题目大意:

T个测试样例。一个长度为N的字符串S,之后Q个[l,r,k],表示一个子串S[l,r],求出第k个该子串的下标。起始坐标为1。不存在输出-1。

数据范围:1≤T≤20,  1≤N≤105,  1≤Q≤105,  1≤l≤r≤N,  1≤k≤N,  |S|=N;

赛后补题。参考题解说后缀树组+划分树+ST表+二分。

比赛的时候只会后缀树组不会划分树,赛后仔细想,觉得后缀数组可以,然而并不,会TLE。

补提的时候先是采用后缀树组+划分树+RMQ+二分,还是TLE了。

之后改成后缀树组+划分树+ST表+二分,RMQ是随手用。

  1 //kkkek
  2 #include<cstdio>
  3 #include<cstring>
  4 #include<algorithm>
  5 #include<cmath>
  6 using namespace std;
  7 typedef long long ll;
  8 const int mod=998244353;
  9 const int maxn=1e5+50;
 10
 11 /*主代码:后缀数组+ST表+划分树+二分check*/
 12
 13 /***************后缀数组**************/
 14 int wa[maxn],wb[maxn],wv[maxn];
 15 int cmp(int *r,int a,int b,int k)
 16 {
 17     return r[a]==r[b]&&r[a+k]==r[b+k];
 18 }
 19 void da(int *r,int *sa,int n,int m,int *ws)
 20 {2019-08-282019-08-282019-08-282019-08-282019-08-28
 21     int i,j,p,*x=wa,*y=wb,*t;
 22     for(i=0;i<m;i++)ws[i]=0;
 23     for(i=0;i<n;i++)ws[x[i]=r[i]]++;
 24     for(i=1;i<m;i++)ws[i]+=ws[i-1];
 25     for(i=n-1;i>=0;i--)sa[--ws[x[i]]]=i;
 26     for(j=1,p=1;p<n;j*=2,m=p)
 27     {
 28         for(p=0,i=n-j;i<n;i++)y[p++]=i;
 29         for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j;
 30         for(i=0;i<n;i++)wv[i]=x[y[i]];
 31         for(i=0;i<m;i++)ws[i]=0;
 32         for(i=0;i<n;i++)ws[wv[i]]++;
 33         for(i=1;i<m;i++)ws[i]+=ws[i-1];
 34         for(i=n-1;i>=0;i--)sa[--ws[wv[i]]]=y[i];
 35         for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
 36         x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
 37     }
 38     return;
 39 }
 40 int height[maxn];
 41 void calheight(int *r,int *sa,int n,int *rank)
 42 {
 43     memset(height,0,sizeof(height));
 44     int i,j,k=0;
 45     for(i=1;i<=n;i++)rank[sa[i]]=i;
 46     for(i=0;i<n;height[rank[i++]]=k)
 47     for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
 48     return;
 49 }//sa[排名]=下标 rank[下标]=排名
 50 //height[排名i]=排名为i的数组与排名为i-1数组的最长前缀的长度
 51
 52 /******************划分树******************/
 53 int sorted[maxn];
 54 int num[20][maxn],val[20][maxn];
 55 void build(int l,int r,int ceng)
 56 {
 57     if(l==r)return;
 58     int mid=(l+r)>>1,isame=mid-l+1,i;
 59     for(i=l;i<=r;i++)if(val[ceng][i]<sorted[mid])isame--;
 60     int ln=l,rn=mid+1;
 61     for(i=l;i<=r;i++)
 62     {
 63         if(i==l)num[ceng][i]=0;
 64         else num[ceng][i]=num[ceng][i-1];
 65         if(val[ceng][i]<sorted[mid]||val[ceng][i]==sorted[mid]&&isame>0)
 66         {
 67             val[ceng+1][ln++]=val[ceng][i];
 68             num[ceng][i]++;
 69             if(val[ceng][i]==sorted[mid])isame--;
 70         }
 71         else val[ceng+1][rn++]=val[ceng][i];
 72     }
 73     build(l,mid,ceng+1);build(mid+1,r,ceng+1);
 74 }
 75 int look(int ceng,int sl,int sr,int l,int r,int k)
 76 {
 77     if(sl==sr)return val[ceng][sl];
 78     int ly;
 79     if(l==sl)ly=0;
 80     else ly=num[ceng][l-1];
 81     int tolef=num[ceng][r]-ly;
 82     if(tolef>=k)
 83     {
 84         return look(ceng+1,sl,(sl+sr)/2,sl+ly,sl+num[ceng][r]-1,k);
 85     }
 86     else
 87     {
 88         int lr=(sl+sr)/2+1+(l-sl-ly);
 89         return look(ceng+1,(sl+sr)/2+1,sr,lr,lr+r-l+1-tolef-1,k-tolef);
 90     }
 91 }//(0,1,n,l,r,k)找数组l与r之间第k大的数的数值,返回该数值
 92
 93 /***********************ST表******************/
 94 int st[maxn][25];
 95 void initST(int n,int *a)
 96 {
 97     memset(st,0,sizeof(st));
 98     for(int i=0;i<=n;i++)st[i][0]=a[i];
 99     for(int j=1;(1<<j)<=n;j++)
100         for(int i=0;i+(1<<j)-1<=n;i++)
101             st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
102 }
103 int askST(int l,int r)
104 {
105     if(l>r)swap(l,r);
106     int k=log2(r-l+1);//(int)(log((double)(r-l+1))/log(2.0));
107     return min(st[l][k],st[r-(1<<k)+1][k]);
108 }//askST(l,r)找数组中l,r之间的最小值,返回该最小值
109 int askRMQ(int ra,int rb)
110 {
111     if(ra>rb)swap(ra,rb);
112     int k=0;
113     while(1<<(k+1)<=rb-ra)k++;
114     return min(st[ra+1][k],st[rb-(1<<k)+1][k]);//len
115 }//askRMQ(rank[a],rank[b])找下标a,b(或者排名ra,rb)的后缀数组的最长公共前缀
116 //前驱套用st表前驱(长得一毛一样当然就不再init一遍啦)
117
118 /******************二分查找*********************************/
119 //用ST表判断长度是否合理  不合理二分缩小查找范围
120 int check(int l,int r,int ju,int len)
121 {
122     if(l==r)return l;
123     int mid=(l+r)>>1;
124     if(!ju)
125     {
126         if(l>r)return l;
127         if(askST(l,r)>=len)return r;
128         if(askST(l,mid)>=len)return check(mid,r-1,0,len);
129         else
130         {
131             if(l+1==mid)return l;
132             return check(l,mid-1,0,len);
133         }
134     }
135     else
136     {
137         if(r<l)return r;
138         if(askST(l,r)>=len)return l;
139         if(askST(mid,r)>=len)return check(l+1,mid,1,len);
140         else
141         {
142             if(mid+1==r)return r;
143             return check(mid+1,r,1,len);
144         }
145     }
146 }//找到给定子串l的rank[l]排位前后的子串的最小下标与最大小标,即,上下界
147
148 int main()
149 {
150     int T;
151     scanf("%d",&T);
152     while(T--)
153     {
154         int n,q,i,j,r[maxn]={0},sa[maxn]={0},ws[maxn]={0},rank[maxn]={0},L,R,k,len,ra;
155         char s[maxn]="\0";
156         scanf("%d%d",&n,&q);
157         scanf("%s",s);
158         for(i=0;i<n;i++)r[i]=s[i]-‘a‘+1;
159         r[n]=0;n++;
160         da(r,sa,n,30,ws);//后缀数组求出sa[]和rank[]
161         calheight(r,sa,n-1,rank);//得height[]数组 为之后查找上下界做准备
162
163         for(i=0;i<n;i++)sorted[i]=sa[i],val[0][i]=sa[i];
164         sort(sorted+1,sorted+n);//为划分树准备
165         build(1,n-1,0);//划分树预处理
166
167         initST(n-1,height);//ST表预处理
168
169         while(q--)
170         {
171             scanf("%d%d%d",&L,&R,&k);
172             L--;R--;//下标与题意下标对应
173
174             ra=rank[L];
175             len=R-L+1;
176
177             //二分查找
178             if(height[ra]<len&&height[ra+1]!=0&&askRMQ(ra,ra+1)>=len)
179             {
180                 L=ra;
181                 R=check(ra+1,n-1,0,len);//这里!一定要ra+1 因为height[].... 为了这个debug好久=皿=
182             }
183             else if(height[ra]<len)
184             {
185                 L=R=ra;
186             }
187             else
188             {
189                 if(height[ra+1]==0)R=ra;
190                 else R=check(ra,n-1,0,len);
191                 L=check(1,ra,1,len);
192             }
193             if(askRMQ(ra,L-1)>=len&&L!=1)L--;
194             if(R-L+1<k)printf("-1\n");
195             else printf("%d\n",look(0,1,n-1,L,R,k)+1);
196         }
197     }
198 }

一点一点打出来,加上debug,好艰难 (: 3_~)_

但是...... 最后ac了就好爽啊o(* ̄︶ ̄*)o?(?╯?╰?)?

2019-08-28

原文地址:https://www.cnblogs.com/kkkek/p/11427299.html

时间: 2024-08-25 05:00:31

K-th occurrence(后缀树组+划分树+ST表+RMQ+二分)的相关文章

POJ 2104 K-th Number(区间第k大数)(平方分割,归并树,划分树)

题目链接: http://poj.org/problem?id=2104 解题思路: 因为查询的个数m很大,朴素的求法无法在规定时间内求解.因此应该选用合理的方式维护数据来做到高效地查询. 如果x是第k个数,那么一定有 (1)在区间中不超过x的数不少于k个 (2)在区间中小于x的数有不到k个 因此,如果可以快速求出区间里不超过x的数的个数,就可以通过对x进行二分搜索来求出第k个数是多少. 接下来,我们来看一下如何计算在某个区间里不超过x个数的个数.如果不进行预处理,那么就只能遍历一遍所有元素.

POJ2104-K-th Number-求区间第K大数(暴力or归并树or划分树)

题目链接:http://poj.org/problem?id=2104 题目意思很简单,就是给你一个序列,查询某区间第K大的数: 方法1:时间复杂度O(N*M):不支持更新操作,代码简单: 利用结构体排序,保留原数据的顺序. #include <stdio.h> #include <iostream> #include <algorithm> #define N 100000 using namespace std; /* 这个思路很好:时间复杂度O(n*m): 不过还

POJ 2104 K-th Number(区间第k大数)(平方切割,归并树,划分树)

题目链接: http://poj.org/problem? id=2104 解题思路: 由于查询的个数m非常大.朴素的求法无法在规定时间内求解. 因此应该选用合理的方式维护数据来做到高效地查询. 假设x是第k个数,那么一定有 (1)在区间中不超过x的数不少于k个 (2)在区间中小于x的数有不到k个 因此.假设能够高速求出区间里不超过x的数的个数.就能够通过对x进行二分搜索来求出第k个数是多少. 接下来,我们来看一下怎样计算在某个区间里不超过x个数的个数. 假设不进行预处理,那么就仅仅能遍历一遍全

HDU 4251 --- 主席树(划分树是正解)

题意:查询区间中位数 思路:模板题,相当于区间第K大的数,主席树可以水过,但划分树是正解.但还没搞明白划分树,先上模板 1 #include <iostream> 2 #include <cstdio> 3 #include <cmath> 4 #include <cstring> 5 #include <cstdlib> 6 #include <string> 7 #include <vector> 8 #include

POJ 2761-Feed the dogs(划分树)求区间内第k小的数

Feed the dogs Time Limit: 6000MS   Memory Limit: 65536K Total Submissions: 17679   Accepted: 5561 Description Wind loves pretty dogs very much, and she has n pet dogs. So Jiajia has to feed the dogs every day for Wind. Jiajia loves Wind, but not the

划分树 静态第k大

划分树是保存了快速排序的过程的树,可以用来求静态第k小的数 如果,划分树可以看做是线段树,它的左孩子保存了mid-L+1 个 小于等于 a[mid] 的数字,  右孩子保存了 R-mid个大于等于a[mid]的数字 数组a是排序过后的数组,而划分树保存的是原数组的数据, 划分树的构造就是将上一层[l,r]个数的 mid-l+1个数划分到左子区间,r-(mid-l+1)个数划分到了右子区间 void build(int l, int r, int rt) { if (l == r) return;

HDU 3473 Minimum Sum 划分树,数据结构 难度:1

http://acm.hdu.edu.cn/showproblem.php?pid=3473 划分树模板题目,需要注意的是划分树的k是由1开始的 划分树: 参考:http://blog.csdn.net/shiqi_614/article/details/8041390 划分树的定义 划分树定义为,它的每一个节点保存区间[lft,rht]所有元素,元素顺序与原数组(输入)相同,但是,两个子树的元素为该节点所有元素排序后(rht-lft+1)/2个进入左子树,其余的到右子树,同时维护一个num域,

归并树 划分树 可持久化线段树(主席树) 入门题 hdu 2665

如果题目给出1e5的数据范围,,以前只会用n*log(n)的方法去想 今天学了一下两三种n*n*log(n)的数据结构 他们就是大名鼎鼎的 归并树 划分树 主席树,,,, 首先来说两个问题,,区间第k大 ,,,, 这个问题的通用算法是 划分树,, 说白一点就是把快速排序的中间结果存起来, 举个栗子 原数列 4 1 8 2 6 9 5 3 7 sorted 1 2 3 4 5 6 7 8 9 ........................... qs[0] 4 1 8 2 6 9 5 3 7 q

poj 2104 K-th Number(划分树)

题目链接:http://poj.org/problem?id=2104 题目分析:该问题给定一段区间中的值,再给定一段查询区间[ql, qr],需要给出该查询区间中的值在排序后的第K大的值: 使用划分树即可解决该问题:划分树的建树的复杂度为O(NlogN),查询一个区间的第K大值的复杂度为O(logN): 代码如下: #include <cstdio> #include <iostream> #include <algorithm> using namespace st