HDU6704 CCPC网络赛 K-th occurrence (后缀数组 + ST表 + 主席树)

题目链接
题解:
一般这种和子串有关的都能扯到后缀数组,由于要找一个子串第 k 个出现的位置,所以先确定范围,一个子串若在原串中出现 x 次,那么这 x 个以该子串开头的后缀一定在所有的后缀中排名是连续的,并且在这些区间内所有的后缀的前缀都是该子串。所以就可以先求出后缀数组 sa 和 lcp 的 height 数组,接着对于每次查询先二分该子串所在排名的满足条件的最左区间与最右区间,可以用ST表查询 height 数组的区间最小值(也可以用线段树,但是我写到一半发现我的的方法不行)。接着就是主席树维护 sa 数组的权值查询区间第 k 小啦!
代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
#define PI acos(-1.0)
#define INF 0x3f3f3f3f3f3f3f3f
#define P pair<ll, int>
#define debug(x) cout << (#x) << ": " << (x) << " "
#define fastio ios::sync_with_stdio(false), cin.tie(0)
const int mod = 1e9 + 7;
const int M = 15 + 10;
const int N = 100000 + 10;

int t, n, q;

// suffix array
int sa[N], height[N], rk[N];
int k, tmp[N];
char s[N];

bool compare_sa(int i,int j){
    if(rk[i]!=rk[j]) return rk[i]<rk[j];
    else{
        int x=i+k<=n?rk[i+k]:-1;
        int y=j+k<=n?rk[j+k]:-1;
        return x<y;
    }
}

void calc_sa(char s[],int *sa){
    for(int i=0;i<=n;i++){
        sa[i]=i;
        rk[i]=i<n?s[i]:-1;
    }
    for(k=1;k<=n;k*=2){
        sort(sa,sa+n+1,compare_sa);
        tmp[sa[0]]=0;
        for(int i=1;i<=n;i++){
            tmp[sa[i]]=tmp[sa[i-1]]+(compare_sa(sa[i-1],sa[i])?1:0);
        }
        for(int i=0;i<=n;i++){
            rk[i]=tmp[i];
        }
    }
}

void calc_lcp(char s[],int *sa,int *lcp){
    for(int i=0;i<=n;i++) rk[sa[i]]=i;
    int h=0;
    lcp[0]=0;
    for(int i=0;i<n;i++){
        int j=sa[rk[i]-1];
        if(h>0) h--;
        for(;j+h<n&&i+h<n;h++){
            if(s[j+h]!=s[i+h]) break;
        }
        lcp[rk[i]-1]=h;
    }
}

// binary search of height
int f[N][M], lg2[N];

void ST_pre()
{
    for(int i = 1; i <= n; i ++) { lg2[i] = log2(i); }
    for(int i = 1; i <= n; i ++) { f[i][0] = height[i]; }
    for(int j = 1; j <= lg2[n]; j ++) {
        for(int i = 1; i <= n - (1<<j) + 1; i ++) {
            f[i][j] = min(f[i][j-1], f[i+(1<<(j-1))][j-1]);
        }
    }
}

int ST_query(int l, int r)
{
    int k = lg2[r - l + 1];
    return min(f[l][k], f[r-(1<<k)+1][k]);
}

int hquery_L(int p, int val)
{
    int l = 1, r = p;
    while(l < r) {
        int mid = l + r >> 1;
        if(ST_query(mid, p) >= val) r = mid;
        else l = mid + 1;
    }
    return r;
}

int hquery_R(int p, int val)
{
    int l = p, r = n;
    while(l < r) {
        int mid = l + r + 1 >> 1;
        if(ST_query(p, mid) >= val) l = mid;
        else r = mid - 1;
    }
    return l;
}

// segment tree of suffix array
struct node {
    int ls, rs;
    int num;
} tree[N<<5];
int rt[N], cnt;

void build(int &now, int l = 1, int r = n)
{
    now = ++cnt;
    tree[now].num = 0;
    if(l == r) return ;
    int mid = (l + r) >> 1;
    build(tree[now].ls, l, mid);
    build(tree[now].rs, mid + 1, r);
}

void add(int &now, int pre, int pos, int l = 1, int r = n)
{
    now = ++cnt;
    tree[now] = tree[pre];
    tree[now].num ++;
    if(l == r) return ;
    int mid = (l + r) >> 1;
    if(pos <= mid) add(tree[now].ls, tree[pre].ls, pos, l, mid);
    else add(tree[now].rs, tree[pre].rs, pos, mid + 1, r);
}

int query(int p, int q, int k, int l = 1, int r = n)
{
    if(l == r) return l;
    int mid = (l + r) >> 1, lcnt = tree[tree[q].ls].num - tree[tree[p].ls].num;
    if(lcnt >= k) return query(tree[p].ls, tree[q].ls, k, l, mid);
    return query(tree[p].rs, tree[q].rs, k - lcnt, mid + 1, r);
}

signed main()
{
    scanf("%d", &t);
    while(t --) {
        scanf("%d %d %s", &n, &q, s);

        calc_sa(s, sa);
        calc_lcp(s, sa, height);
        for(int i = 1; i <= n; i ++) sa[i] ++;  // all numbers must greater than zero
        for(int i = n; i >= 1; i --) rk[i] = rk[i - 1];
        for(int i = n; i >= 1; i --) height[i] = height[i - 1];

        ST_pre();

        cnt = 0;
        build(rt[0], 1, n);
        for(int i = 1; i <= n; i ++) {
            add(rt[i], rt[i - 1], sa[i]);
        }

        for(int i = 1, l, r, k; i <= q; i ++) {
            scanf("%d %d %d", &l, &r, &k);
            int rkl, rkr;
            if(height[rk[l]] < r - l + 1) rkl = rk[l];
            else rkl = hquery_L(rk[l], r - l + 1) - 1;
            if(height[rk[l] + 1] < r - l + 1) rkr = rk[l];
            else rkr = hquery_R(rk[l] + 1, r - l + 1);
            int ans = -1;
            if(rkr - rkl + 1 >= k) ans = query(rt[rkl - 1], rt[rkr], k);
            printf("%d\n", ans);
        }
    }
    return 0;
}

/*

             ,----------------,              ,---------,
        ,-----------------------,          ,"        ,"|
      ,"                      ,"|        ,"        ,"  |
     +-----------------------+  |      ,"        ,"    |
     |  .-----------------.  |  |     +---------+      |
     |  |                 |  |  |     | -==----`|      |
     |  |                 |  |  |     |         |      |
     |  |    Accepted _   |  |  |/----|`---=    |      |
     |  |                 |  |  |   ,/|==== OOO |      ;
     |  |                 |  |  |  // |(((( [33]|    ,"
     |  `-----------------`  |," .//| |((((     |  ,"
     +-----------------------+  //  | |         |,"
        /_)______________(_/  //`   | +---------+
   ___________________________/___  `,
  /      Rejoicing in hope       /,   \,"-----------,
 /   patient in tribulation     //   ,`\--{)B     ,"
/_==__==========__==_===__====_/`   /___________,"

*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值