2019 ccpc网络赛 hdu6704 K-th occurrence

2 篇文章 0 订阅
1 篇文章 0 订阅

题意:给你一个字符串,再给你q个询问,每一个询问有l,r,k, 求字符串中下标l到下标r这个子串第k次出现在字符串中的位置,不存在则输出-1.

后缀数组:将所有后缀排序,height数组是比较第i个后缀和第i-1个后缀的最长公共前缀的长度

rmq:求区间的最小/最大数

主席树:求区间第k大的数

首先求出后缀数组(包括height函数),对于每一个询问,我们知道它的开始下标是i,通过后缀数组中的rk[i](表示下标为i的后缀排完序后对应的位置)得到在排好序的所有后缀的位置,现在通过h函数找到公共前缀长度>=r - l + 1的上限和下限,但是如果直接找最坏情况会变成O(n),优化方法是因为h函数的特殊性,懂后缀数组的都知道h[i]>=h[i-1]-1,那么我们先把所有区间的h的最小值用ST表统计出来,然后二分2~rk[l] 和 rk[l + 1] + 1 ~ n找上下限(二分条件是h[mid]>=r - l + 1),找到上下限后,我们现在要求第k个的位置,用主席树预处理下就行了,然后按照主席树的方法查询答案即可。

注意点:上下限的边界情况,如果rk[2]满足,那么下限是1,如果rk[l + 1]不满足,那么上限是rk[l]

 

/**/
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cctype>
#include <iostream>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <string>
#include <stack>
#include <queue>

typedef long long LL;
using namespace std;

int n, t, q, m, cnt;
char s[100005];
int rt[100005], sa[100005], x[100005], y[100005], c[100005], h[100005], rk[100005];
int dp[25][100005], bin[25], Log[100005];

struct node
{
    int l, r;
    int w;
}tr[100005 * 20];

void read(int &x){
	char ch = getchar();x = 0;
	for (; ch < '0' || ch > '9'; ch = getchar());
	for (; ch >='0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
}

void cpy(int &now, int old){
    now = ++cnt;
    tr[now] = tr[old];
}

void up(int &now){
    tr[now].w = tr[tr[now].l].w + tr[tr[now].r].w;
}

void build(int &now, int l, int r){
    now = ++cnt;
    tr[now].w = 0;
    if(l == r) return ;
    int mid = (l + r) >> 1;
    build(tr[now].l, l, mid);
    build(tr[now].r, mid + 1, r);
}

void update(int &now, int old, int l, int r, int x){
    cpy(now, old);
    if(l == r && x == l){
        tr[now].w++;
        return ;
    }
    int mid = (l + r) >> 1;
    if(x <= mid) update(tr[now].l, tr[old].l, l, mid, x);
    else update(tr[now].r, tr[old].r, mid + 1, r, x);
    up(now);
}

int query(int s, int t, int l, int r, int k){
    if(l == r) return l;
    int mid = (l + r) >> 1;
    int ans = tr[tr[t].l].w - tr[tr[s].l].w;
    if(k <= ans) return query(tr[s].l, tr[t].l, l, mid, k);
    else return query(tr[s].r, tr[t].r, mid + 1, r, k - ans);
}

void get_sa(){
    for (int i = 1; i <= n; i++) ++c[x[i] = s[i] - 'a' + 1];
    for (int i = 2; i <= m; i++) c[i] += c[i - 1];
    for (int i = n; i >= 1; i--) sa[c[x[i]]--] = i;
    for (int k = 1; k <= n; k <<= 1){
    int cnt1 = 0;
    for (int i = n - k + 1; i <= n; i++) y[++cnt1] = i;
    for (int i = 1; i <= n; i++) if(sa[i] > k) y[++cnt1] = sa[i] - k;
    for (int i = 1; i <= m; i++) c[i] = 0;
    for (int i = 1; i <= n; i++) ++c[x[i]];
    for (int i = 2; i <= m; i++) c[i] += c[i - 1];
    for (int i = n; i >= 1; i--) sa[c[x[y[i]]]--] = y[i], y[i] = 0;
    swap(x, y);
    x[sa[1]] = 1, cnt1 = 1;
    for (int i = 2; i <= n; i++) x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k] ? cnt1 : ++cnt1);
      if(cnt1 == n) break;
      m = cnt1;
    }
}

void get_h(){
    int k = 0;
    for (int i = 1; i <= n; i++) rk[sa[i]] = i;
    for (int i = 1; i <= n; i++){
      if(rk[i] == 1) continue;
      if(k) k--;
      int j = sa[rk[i] - 1];
      while(i + k <= n && j + k <= n && s[i + k] == s[j + k]) k++;
      h[rk[i]] = k;
    }
}

int rmq1(int l, int r, int len){
	int ans = r, R = r;
	while(l <= r){
		int mid = (l + r) >> 1;
		int L = mid, tp = Log[R - L + 1];
		int minn = min(dp[tp][L], dp[tp][R - bin[tp] + 1]);
		if(minn >= len) ans = mid, r = mid - 1;
		else l = mid + 1;
	}
	return ans;
}

int rmq2(int l, int r, int len){
	int ans = l, L = l;
	while(l <= r){
		int mid = (l + r) >> 1;
		int R = mid, tp = Log[R - L + 1];
		int minn = min(dp[tp][L], dp[tp][R - bin[tp] + 1]);
		if(minn >= len) ans = mid, l = mid + 1;
		else r = mid - 1;
	}
	return ans;
}

int main()
{
	//freopen("in.txt", "r", stdin);
	//freopen("out.txt", "w", stdout);

	bin[0] = 1;
	for (int i = 1; i <= 20; i++) bin[i] = bin[i - 1] << 1;
	Log[0] = -1;
	for (int i = 1; i <= 100000; i++) Log[i] = Log[i / 2] + 1;
	scanf("%d", &t);
	while(t--){
		memset(c, 0, sizeof(c));
		// memset(h, 0, sizeof(h));
		scanf("%d %d", &n, &q);
		scanf("%s", s + 1);
		m = 26;
		get_sa();
		get_h();
		cnt = 0;
        build(rt[0], 1, n);
        for (int i = 1; i <= n; i++) update(rt[i], rt[i - 1], 1, n, sa[i]);
        for (int i = 1; i <= n; i++) dp[0][i] = h[i];
        for (int i = 1; i <= Log[n]; i++){
        	for (int j = 1; j + bin[i] - 1 <= n; j++){
        		dp[i][j] = min(dp[i - 1][j], dp[i - 1][j + bin[i - 1]]);
        	}
        }
	    for (int i = 1, l, r, k; i <= q; i++){
	    	scanf("%d %d %d", &l, &r, &k);
	    	int tl = rmq1(2, rk[l], r - l + 1), tr = rmq2(rk[l] + 1, n, r - l + 1);
	    	if(h[tl] >= r - l + 1) tl--;
	    	if(h[tr] < r - l + 1) tr--;
	    	if(tr - tl + 1 < k) printf("-1\n");
	    	else printf("%d\n", query(rt[tl - 1], rt[tr], 1, n, k));
	    }
	}

	return 0;
}
/**/

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值