hdu6704 2019CCPC网络选拔赛1003 K-th occurrence 后缀数组
题意:给你一个长度为n的字符串,有q个询问,每次询问一个子串s(l,r)第k次出现的位置,若子串出现次数少于k次输出-1.
解题思路:先把SA跑出来,然后对于每次询问可以由l和rank[]找到l在所有后缀中的排名,再用两次二分求出使得LCP(L,R)包含s(l,r)的最大区间[L,R],LCP可以借助height[]的性质和ST表求得,即[L,R]包含rank[l]且min{height[L+1],height[L+2],...,height[R]}>=r-l+1。现在问题就转化为了求[L,R]中第k大的sa[i],这个就是主席树经典操作了。
感觉这个做法挺容易想出来的,但是就是写起来有些麻烦,对SA不熟悉的话就容易写劈叉。
听机房大佬说还有后缀自动机+线段树合并的写法:传送门
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
const int INF=0x3f3f3f3f;
char s[maxn];
int height[maxn],c[maxn],x[maxn],y[maxn],sa[maxn],rk[maxn];
void SA(int n){
n++;
int i,j,k,m=128;
for(i=0;i<m;i++) c[i]=0;
for(i=0;i<n;i++)c[x[i]=s[i]]++;
for(i=1;i<m;i++) c[i]+=c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
for(j=1;j<=n;j<<=1){
k=0;
for(i=n-j;i<n;i++) y[k++]=i;
for(i=0;i<n;i++) if(sa[i]>=j) y[k++]=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);
m=0;
x[sa[0]]=m++;
for(i=1;i<n;i++){
if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+j]==y[sa[i-1]+j]) x[sa[i]]=m-1;
else x[sa[i]]=m++;
}
if(m>=n) break;
}
k=0;
for(i=0;i<n;i++) rk[sa[i]]=i;
for(i=0;i<n-1;i++){
if(k) k--;
j=sa[rk[i]-1];
while(s[i+k]==s[j+k]) k++;
height[rk[i]]=k;
}
// cout<<"sa:";for(int i=0;i<n;i++)cout<<sa[i]<<" ";cout<<endl;
// cout<<"rk:";for(int i=0;i<n;i++)cout<<rk[i]<<" ";cout<<endl;
// cout<<"h:";for(int i=1;i<n-1;i++)cout<<height[i]<<" ";cout<<endl;
//
// for(int i=0;i<n;i++){
// for(int j=sa[i];j<n;j++)putchar(s[j]);
// putchar('\n');
// }
}
int n,m;
int mi[maxn][20],lg[maxn];
void initRMQ()
{
memset(mi,0x3f,sizeof(mi));
lg[1]=0;
for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
for(int i=1;i<=n;i++)mi[i][0]=height[i];
for(int j=1;j<=lg[n];j++)
for(int i=1;i<=n;i++)
mi[i][j]=min(mi[i][j-1],mi[i+(1<<(j-1))][j-1]);
}
int queryRMQ(int x,int y)
{
int l=min(x,y)+1,r=max(x,y);
int d=lg[r-l+1];
//cout<<"llrr:"<<l<<" "<<r<<" "<<min(mi[l][d],mi[r-(1<<d)+1][d])<<endl;
return min(mi[l][d],mi[r-(1<<d)+1][d]);;
}
int T[maxn],cnt;
int sum[maxn<<5],L[maxn<<5],R[maxn<<5];
inline int build(int l,int r)
{
int rt=++cnt;
sum[rt]=0;
int mid=(l+r)/2;
if(l<r){
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;
int mid=(l+r)/2;
if(l<r){
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)
{
if(l>=r)return l;
int mid=(l+r)/2;
int x=sum[L[v]]-sum[L[u]];
if(x>=k)return query(L[u],L[v],l,mid,k);
else return query(R[u],R[v],mid+1,r,k-x);
}
int solve(int l,int r,int k)
{
int len=r-l+1;
int lp=rk[l],rp=rk[l];
int ll=0,rr=rk[l],mid;
int mii;
while(ll<=rr){
mid=(ll+rr)/2;
mii=queryRMQ(mid,rk[l]);
if(mii>=len)lp=mid,rr=mid-1;
else ll=mid+1;
}
ll=rk[l]+1,rr=n;
while(ll<=rr){
mid=(ll+rr)/2;
mii=queryRMQ(rk[l],mid);
if(mii>=len)rp=mid,ll=mid+1;
else rr=mid-1;
}
// cout<<"lr:"<<rk[l]<<" "<<lp<<" "<<rp<<endl;
if(rp-lp+1<k)return -1;
return query(T[lp-1],T[rp],0,n-1,k)+1;
}
int main()
{
//#ifndef ONLINE_JUDGE
// freopen("in.txt","r",stdin);
//#endif
int Case;
scanf("%d",&Case);
while(Case--){
scanf("%d %d",&n,&m);
scanf("%s",s);
SA(n);
initRMQ();
cnt=0;
T[0]=build(0,n-1);
for(int i=1;i<=n;i++)T[i]=update(T[i-1],0,n-1,sa[i]);
int l,r,k;
while(m--){
scanf("%d %d %d",&l,&r,&k);
printf("%d\n",solve(l-1,r-1,k));
}
}
return 0;
}
hdu6704 2019CCPC网络选拔赛1003 K-th occurrence 后缀数组的更多相关文章
- hdu6704 2019CCPC网络选拔赛1003 K-th occurrence 后缀自动机+线段树合并
解题思路: fail树上用权值线段树合并求right/endpos集合,再用倍增找到待查询串对应节点,然后权值线段树求第k大. #include<bits/stdc++.h> using ...
- [2019CCPC网络赛][hdu6704]K-th occurrence(后缀数组&&主席树)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6704 题意为查询子串s[l...r]第k次出现的位置. 写完博客后5分钟的更新 写完博客才发现这份代码 ...
- 2019CCPC网络预选赛 1003 K-th occurrence 后缀自动机 + 二分 + 主席树
题意:给你一个长度为n的字符串,有m次询问,每次询问l到r的子串在原串中第k次出现的位置,如果没有输出-1.n, m均为1e5级别. 思路:后悔没学后缀数组QAQ,其实只要学过后缀数组这个题还是比较好 ...
- HDU - 6704 K-th occurrence (后缀数组+主席树/后缀自动机+线段树合并+倍增)
题意:给你一个长度为n的字符串和m组询问,每组询问给出l,r,k,求s[l,r]的第k次出现的左端点. 解法一: 求出后缀数组,按照排名建主席树,对于每组询问二分或倍增找出主席树上所对应的的左右端点, ...
- 2017CCPC 网络选拔赛1003 Ramsey定理
Ramsey定理 任意6个人中,一定有三个人互为朋友,或者互相不是朋友. 证明 这里我就不证明了.下面链接有证明 鸽巢原理 Ramsey定理 AC代码 #include <stdio.h> ...
- 2017中国大学生程序设计竞赛 - 网络选拔赛 1003 HDU 6152 Friend-Graph (模拟)
题目链接 Problem Description It is well known that small groups are not conducive of the development of ...
- 2019CCPC网络选拔赛 hdu6703 array(主席树+set)
题意 给你一个1~n的排列,由两种操作: 1 pos:将a[pos]+10 000 000 2 r k:求大于等于k且不等于a[1~r]的数的最小值. 强制在线. 思路 如果没有1操作,那么我们直接主 ...
- hdu6075 2019CCPC网络选拔赛1004 path
题意:给定一个带权有向图,有q组询问,每次询问在有向图的所有路径中,第k小的路径权值 解题思路:因为k最大只有5e4,考虑暴力搜索出前maxk小的路径并用数组记录权值,然后就可以O(1)查询. 具体实 ...
- hdu 6704 K-th occurrence(后缀数组+可持久化线段树)
Problem Description You are given a string S consisting of only lowercase english letters and some q ...
随机推荐
- C#-Func<>
与C#-Action十分相似, Func<> 也是c#内置的委托类型,不同的是, Func<> 只能用来引用具有返回值的方法,也就是说,在使用它时,至少需要在尖括号内指定一种类 ...
- C#设计模式之18-备忘录模式
备忘录模式(Memento Pattern) 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/421 访问. 备忘录模式属 ...
- C#LeetCode刷题之#532-数组中的K-diff数对(K-diff Pairs in an Array)
问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3716 访问. 给定一个整数数组和一个整数 k, 你需要在数组里找 ...
- 生成对抗网络GAN介绍
GAN原理 生成对抗网络GAN由生成器和判别器两部分组成: 判别器是常规的神经网络分类器,一半时间判别器接收来自训练数据中的真实图像,另一半时间收到来自生成器中的虚假图像.训练判别器使得对于真实图像, ...
- Homekit_Dohome_智能灯带
简介:本款产品支持音乐律动控制,可以随音乐改换颜色及频率,可以使用Homekit或者Dohome或者遥控器进行有效控制,同时Dohome App已经对接了各大智能音箱,下载Dohome App后就可以 ...
- JavaFX桌面应用-视频转码工具(支持爱奇艺qsv转mp4)
最近由于需要将在爱奇艺下载的视频(qsv)转化了mp4,用JavaFX开发一个视频转码工具,算是JavaFX开发的第一个应用吧. 支持qsv转码mp4,理论上支持各种格式,仅测试了flv,qsv格式. ...
- Disruptor极速队列
参考:http://www.cnblogs.com/haiq/p/4112689.html Disruptor 是线程内通信框架,用于线程里共享数据.LMAX 创建Disruptor作为可靠消息架构的 ...
- 一进“dos”就自动进入上次的目录
这个原来在别的地方发过,但是后来发现有bug,处理windows下带有空格的长文件夹会出错.因为原来那个帖子已经不能编辑了,所以修改后写在这里. 这里的“dos”,就是windows下的命令行窗口,所 ...
- Wireshark中的Checksum: 0x90c5 [validation disabled]问题
Wireshark中的Checksum: 0x90c5 [validation disabled]问题 废话不多说先上问题图: 这是我在做关于DNS协议PPT的时候出现的协议树第五项展开结果,可以发现 ...
- linux驱动之定时器的介绍和内核时间的学习
本文章摘自下面的网友: http://blog.sina.com.cn/s/blog_6e5b342e0100m87d.html 一.内核中如何记录时间 任何程序都需要时间控制,其主要目的是: 测量时 ...