hdu6704 后缀数组+主席树+ST +二分

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6704

推荐博客:https://www.cnblogs.com/1625--H/p/11403199.html

题意:输入t,接下来t组数据,每组数据输入n和q,n代表字符串长度,q代表查询次数,接下来一行输入长度为n的字符串,最后再输入q行查询,每一个查询有l,r,k,查询字符串中在区间[l,r]里的这个子串第k次出现的位置,位置就是出现这个子串的首字母位置,如果没有第k次,则输出-1。

比赛时我也没写出来,补题。

思路:我讲的可能不是很清楚,可以看上面的推荐博客。

区间[l,r]的子串在位置为l的后缀中是一定出现了的,并且是这个后缀的一个前缀,而要找到其他出现这个子串的位置,我们可以用后缀数组,先用后缀数组先把原串所有的后缀进行排序,即求出sa数组,其中出现这个子串的后缀肯定都是连续的,因为它都是这些连续后缀的前缀,这里我们可以先求出后缀数组中的height[i]数组,i是排位,height[i]代表排位为i的后缀和排位为i-1的后缀之间的最长公共前缀(LCP),我们以位置为l的后缀的排位x[l]为中心,向左右两边分别找最后一个和位置为l的后缀之间LCP>=r-l+1的后缀,得到两个后缀的排位(假设分别是L和R),那么在排位区间[L,R]中出现的所有后缀的前缀都包含着我们要查询的子串,这个过程可以用二分来实现。我们要找其中出现的第k个,实际上就只需要寻找这个排位区间中的位置第k大,这个我们可以用静态主席树做到。

代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<cmath>
#include<vector>
#include<set>
#include<cstdio>
#include<string>
#include<deque>
using namespace std;
typedef long long LL;
#define eps 1e-8
#define INF 0x3f3f3f3f
#define maxn 100005
int sa[maxn],height[maxn],x[maxn],y[maxn],c[maxn];
//这里x数组对应于rank数组,但是我个人习惯写x了
int n,m,k,t,q,cnt;
char s[maxn];
int dp[maxn][20],root[maxn];
struct node{//静态主席树也是用结构体写的
    int l,r,sum;
}tree[maxn<<6];
void getHeight(){//height数字下标表示排位,值表示排位为i的后缀和排位为i-1的后缀的LCP
                //H数组下标表示位置,H[i]=height[x[i]],有H[i]>=H[i-1]-1
    int k=0;
    for(int i=1;i<=n;i++) x[sa[i]]=i;
    for(int i=1;i<=n;i++){
        if(k) k--;
        int j=sa[x[i]-1];//找到位置在i的关键字的前一个排位的关键字
        while(s[i+k]==s[j+k]&&(i+k)<=n&&(j+k)<=n) k++;
        height[x[i]]=k;
    }
}
void build_sa(){//好久没写这个了,所以写的时候加了很多注释,就当梳理思路,复习了
    m=256;
    for(int i=1;i<=m;i++) c[i]=0;
    for(int i=1;i<=n;i++) c[x[i]=s[i]]++;//x数组下标表示位置,值表示排位
    for(int i=1;i<=m;i++) c[i]+=c[i-1];
    for(int i=n;i>=1;i--) sa[c[x[i]]--]=i;//sa数组下标表示排位,值表示第一关键字的位置
    for(int k=1;k<n;k<<=1){
        int num=0;
        for(int i=n-k+1;i<=n;i++) y[++num]=i;//y数组下标表示第二关键字排位,值表示对应的第一关键字的位置
        // 有部分第一关键字没有第二关键字,这里把它们的第二关键字补‘0‘,按照出现顺序排序
        for(int i=1;i<=n;i++){//按照关键字排位从小到大来遍历第一关键字
            if(sa[i]>k)//排位为i的第一关键字位置如果大于k,则说明这个关键字可以作为另一个关键字的第二关键字
            y[++num]=sa[i]-k;// 这个关键字作为第二关键字的话,它所对应的第一关键字的位置就是sa[i]-k
        }

        //上面已经求出了排位为i的第一关键字和第二关键字的对应的位置,下面开始对第一关键字和对应的第二关键字进行合并
        //并求出合并之后的关键字对应的sa数组,下标表示排位,值表示位置
        for(int i=1;i<=m;i++) c[i]=0;
        for(int i=1;i<=n;i++) c[x[i]]++;
        for(int i=1;i<=m;i++) c[i]+=c[i-1];
        //我们这里i从大到小,在第一关键字不同时,这里的y[i]没有什么影响,但是在第一关键字相同时,第二关键字
        //更大的关键字会先出现,从而获得更大的位置,这就保证了先比较第一关键字,相同时比较第二关键字
        for(int i=n;i>=1;i--) sa[c[x[y[i]]]--]=y[i];

        swap(x,y);//接下来我们要求出合并之后关键字的x数组,下标表示位置,值表示排位,所以我们把x数组的值复制到y数组
        num=1;
        x[sa[1]]=1;
        for(int i=2;i<=n;i++)//按照排位从小到大的顺序遍历y数组
        x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?num:++num;
        m=num;
        if(m>=n) break;
    }
}
void ST(){
    for(int i=1;i<=n;i++)
    dp[i][0]=height[i];//i代表的是排位
    for(int j=1;(1<<j)<=n;j++){
        for(int i=1;i+(1<<j)-1<=n;i++){
            int a=dp[i][j-1];
            int b=dp[i+(1<<(j-1))][j-1];
            dp[i][j]=min(a,b);
        }
    }
}
void init(){
    memset(root,0,sizeof(root));
    memset(height,0,sizeof(height));
    memset(dp,0,sizeof(dp));
    cnt=0;
}
void build_0(int &root,int l,int r){
    root=++cnt;
    tree[root].sum=0;//在这个位置卡了一天,把他写在下面了,并且这个函数中没有向上更新
    if(l==r){
        return;
    }
    int mid=(l+r)/2;
    build_0(tree[root].l,l,mid);
    build_0(tree[root].r,mid+1,r);
}
void update(int root){
    tree[root].sum=tree[tree[root].l].sum+tree[tree[root].r].sum;
}
void build(int pre,int &root,int l,int r,int index){
    root=++cnt;
    tree[root]=tree[pre];
    if(l==r){
        tree[root].sum=1;
        return;
    }
    int mid=(l+r)/2;
    if(index<=mid)
    build(tree[pre].l,tree[root].l,l,mid,index);
    else
    build(tree[pre].r,tree[root].r,mid+1,r,index);
    update(root);
}
int RMQ(int l,int r){//这里写的有点麻烦,这里的l和r已经是后缀的排位了
    if(l>r) swap(l,r);
    if(l==r) return n-sa[l]+1;//l==r一定满足条件,因为就是位置为l的后缀,这里返回INF也是可以的
    l++;//这里需要l++,因为考虑到height[i]的定义
        //当我们要查询排位在区间[l,r]之间的后缀,它们两两相邻的LCP最小值,l需要++,
        //因为height[l]是排位为l的后缀和排位为l-1的后缀的LCP
        //这里我们是不需要height[l]的,只需要height[l+1]到height[r]
    int k=0;
    while((1<<(k+1))<=r-l+1) k++;
    int a=dp[l][k];
    int b=dp[r-(1<<k)+1][k];
    return min(a,b);
}
int find_L(int l,int r,int len){
    int R=r;
    int pre=r;//其实还是不怎么会二分的,写成这个方式比较保险
    while(l<=r){
        int mid=(l+r)/2;
        if(RMQ(mid,R)>=len){
            pre=mid;
            r=mid-1;
        }
        else
        l=mid+1;
    }
    return pre;
}
int find_R(int l,int r,int len){
    int L=l;
    int pre=l;
    while(l<=r){
        int mid=(l+r)/2;
        if(RMQ(L,mid)>=len){
            pre=mid;
            l=mid+1;
        }
        else
        r=mid-1;
    }
    return pre;
}
int ask(int pre,int root,int l,int r,int k){
    if(tree[root].sum-tree[pre].sum<k) return -1;
    if(l==r){
        return l;
    }
    int mid=(l+r)/2;
    if(tree[tree[root].l].sum-tree[tree[pre].l].sum>=k)
    return ask(tree[pre].l,tree[root].l,l,mid,k);
    else
    return ask(tree[pre].r,tree[root].r,mid+1,r,k-(tree[tree[root].l].sum-tree[tree[pre].l].sum));
}
int main()
{
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&q);
        init();
        scanf("%s",s+1);
        build_sa();
        getHeight();
        build_0(root[0],1,n);//第0个版本的线段树,我这里直接写build_0了,个人习惯了
        for(int i=1;i<=n;i++)//其他版本的线段树
        build(root[i-1],root[i],1,n,sa[i]);
        ST();
        int l,r;
        while(q--){
            scanf("%d%d%d",&l,&r,&k);
            int len=r-l+1;//相邻p排位两个后缀的LCP最小要是len
            int L=find_L(1,x[l],len);//二分找左边界
            int R=find_R(x[l],n,len);//二分找右边界
            LL ans;
            if(R-L+1<k) ans=-1;
            else ans=ask(root[L-1],root[R],1,n,k);//两个历史版本的线段树中查询第k大的位置
            printf("%lld\n",ans);
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/6262369sss/p/11439511.html

时间: 2024-11-08 03:49:45

hdu6704 后缀数组+主席树+ST +二分的相关文章

hdu 6704 K-th occurrence 二分 ST表 后缀数组 主席树

我们考虑,一个子串必定是某个后缀的前缀. 排序相邻的后缀他们的前缀一定最相似. 所以全部的一种子串必定是一些排序相邻的后缀的公共前缀. 从l开始的子串,则从rank[l]开始看,两侧height保证大于子串长度,能延伸多长,则证明有多少个这种子串. 我们用ST表维护出height的最小值,然后通过最小值二分即可,边界有些棘手. 然后我们就得到了一个height不小于子串长度的连续区间,这个区间是以原后缀的字典序排序的. 而同时,sa数组下标为排序,值为原串位置. 所以我们对这个区间在sa数组上做

HDU-6704 K-th occurrence(后缀数组+主席树)

题意 给一个长度为n的字符串,Q次询问,每次询问\((l,r,k)\) , 回答子串\(s_ls_{l+1}\cdots s_r\) 第\(k\) 次出现的位置,若不存在输出-1.\(n\le 1e5,Q\le 1e5\) 分析 查询子串第 k 次出现的位置,很容易想到要用处理字符串的有力工具--后缀数组. 那么该怎么用呢?我们先把样例的字符串的每个后缀排个序,然后对样例进行模拟 原串:aaabaabaaaab 排名 后缀 位置 1 aaaab 8 2 aaab 9 3 aaabaabaaab

2019 CCPC 网络赛第三题 K-th occurrence 后缀数组+划分树+ST表+二分

题意:给你一个长度为n的字符串,每次询问给出三个数:L , R , K,表示原串 L 到 R 的子串在原串第K次出现的首字母的位置 解题思路:对子串的大量操作,不难想到后缀数组(后缀树/后缀自动机不会,所以没想到),注意到子串s[L.....R]必然是某一个后缀的前缀,所以所有前缀是该子串的后缀的排名(即rank数组的值)必定连续,也就是说在后缀数组(sa数组)中,下标是连续的,那么就是求区间第K大了(因为sa数组的值代表的是在字符串中的位置)(这里区间第K大我用划分树求),至于这一段区间的起点

HDU - 6704 K-th occurrence (后缀数组+主席树/后缀自动机+线段树合并+倍增)

题意:给你一个长度为n的字符串和m组询问,每组询问给出l,r,k,求s[l,r]的第k次出现的左端点. 解法一: 求出后缀数组,按照排名建主席树,对于每组询问二分或倍增找出主席树上所对应的的左右端点,求第k大的下标即可. 1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1e5+10,mod=998244353; 5 char buf[N]; 6 int s[N],sa[

【BZOJ】1146: [CTSC2008]网络管理Network(树链剖分+线段树套平衡树+二分 / dfs序+树状数组+主席树)

第一种做法(时间太感人): 这题我真的逗了,调了一下午,疯狂造数据,始终找不到错. 后来发现自己sb了,更新那里没有打id,直接套上u了.我.... 调了一下午啊!一下午的时光啊!本来说好中午A掉去学习第二种做法,噗 好吧,现在第一种做法是hld+seg+bst+二分,常数巨大,log^4级别,目前只会这种. 树剖后仍然用线段树维护dfs序区间,然后在每个区间建一颗平衡树,我用treap,(这题找最大啊,,,囧,并且要注意,这里的rank是比他大的数量,so,我们在二分时判断要判断一个范围,即要

HUID 5558 Alice&#39;s Classified Message 后缀数组+单调栈+二分

http://acm.hdu.edu.cn/showproblem.php?pid=5558 对于每个后缀suffix(i),想要在前面i - 1个suffix中找到一个pos,使得LCP最大.这样做O(n^2) 考虑到对于每一个suffix(i),最长的LCP肯定在和他排名相近的地方取得. 按排名大小顺序枚举位置,按位置维护一个递增的单调栈,对于每一个进栈的元素,要算一算栈内元素和他的LCP最大是多少. 如果不需要输出最小的下标,最大的直接是LCP(suffix(st[top]),  suff

【bzoj1146】[CTSC2008]网络管理Network 倍增LCA+dfs序+树状数组+主席树

题目描述 M公司是一个非常庞大的跨国公司,在许多国家都设有它的下属分支机构或部门.为了让分布在世界各地的N个部门之间协同工作,公司搭建了一个连接整个公司的通信网络.该网络的结构由N个路由器和N-1条高速光缆组成.每个部门都有一个专属的路由器,部门局域网内的所有机器都联向这个路由器,然后再通过这个通信子网与其他部门进行通信联络.该网络结构保证网络中的任意两个路由器之间都存在一条直接或间接路径以进行通信. 高速光缆的数据传输速度非常快,以至于利用光缆传输的延迟时间可以忽略.但是由于路由器老化,在这些

【bzoj3744】Gty的妹子序列 分块+树状数组+主席树

题目描述 我早已习惯你不在身边, 人间四月天 寂寞断了弦. 回望身后蓝天, 跟再见说再见…… 某天,蒟蒻Autumn发现了从 Gty的妹子树(bzoj3720) 上掉落下来了许多妹子,他发现 她们排成了一个序列,每个妹子有一个美丽度. Bakser神犇与他打算研究一下这个妹子序列,于是Bakser神犇问道:"你知道区间 [l,r]中妹子们美丽度的逆序对数吗?" 蒟蒻Autumn只会离线乱搞啊……但是Bakser神犇说道:"强制在线." 请你帮助一下Autumn吧.

【XSY1551】往事 广义后缀数组 线段树合并

题目大意 给你一颗trie树,令\(s_i\)为点\(i\)到根的路径上的字符组成的字符串.求\(max_{u\neq v}(LCP(s_u,s_v)+LCS(s_u,s_v))\) \(LCP=\)最长公共前缀,\(LCS=\)最长公共后缀 \(1\leq n\leq 200000\),字符集为\(\{0\ldots 300\}\) 题解 我们先看看这个\(LCP(s_u,s_v)\)怎么求 广义后缀自动机不行.广义后缀树可能可以,但我不会.广义后缀数组可以.然后我就开始手推广义后缀数组 广义