K-th occurrence HDU - 6704 (SA, 主席树)

大意: 给定串$s$, $q$个询问$(l,r,k)$, 求子串$s[l,r]$的第$k$次出现位置.

本来是个简单签到题, 可惜比赛的时候还没学$SA$...... 好亏啊

相同的子串在$SA$中是一定是连续的一段$[L,R]$

满足对于$L<i\le R$都有$h_i\ge r-l+1$

可以先用线段树二分出$L,R$, 然后主席树查询第$k$大即可

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#define REP(i,a,n) for(int i=a;i<=n;++i)
#define PER(i,a,n) for(int i=n;i>=a;--i)
#define lc (o<<1)
#define rc (lc|1)
#define mid ((l+r)>>1)
#define ls lc,l,mid
#define rs rc,mid+1,r
using namespace std;
const int N = 1e5+10;
int n, q, tot, a[N], T[N];
struct {int l,r,v;} tr[N*40];
char s[N];
int c[N],rk[N],h[N],sa[N],mi[N<<2];

void build(int *a, int n, int m) {
	a[n+1] = 0;
    int i,*x=rk,*y=h;
    for(i=1;i<=m;i++) c[i]=0;
    for(i=1;i<=n;i++) c[x[i]=a[i]]++;
    for(i=1;i<=m;i++) c[i]+=c[i-1];
    for(i=n;i;i--) sa[c[x[i]]--]=i;
    for(int k=1,p;k<=n;k<<=1) {
        p=0;
        for(i=n-k+1;i<=n;i++) y[++p]=i;
        for(i=1;i<=n;i++) if(sa[i]>k) y[++p]=sa[i]-k;
        for(i=1;i<=m;i++) c[i]=0;
        for(i=1;i<=n;i++) c[x[y[i]]]++;
        for(i=1;i<=m;i++) c[i]+=c[i-1];
        for(i=n;i;i--) sa[c[x[y[i]]]--]=y[i];
        swap(x,y); x[sa[1]]=1; p=1;
        for(i=2;i<=n;i++)
            x[sa[i]]=(y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+k]==y[sa[i]+k])?p:++p;
        if(p==n) break; m=p;
    }
    for(i=1;i<=n;i++) rk[sa[i]]=i;
    for(int i=1,j,k=0;i<=n;i++){
        if(k) k--;
        j=sa[rk[i]-1];
        while (a[i+k]==a[j+k]) k++;
        h[rk[i]] = k;
    }
}
//求最小的位置p, 使得[p,x]的最小值>=v
int find1(int o, int l, int r, int x, int v) {
	if (r<=x) {
		if (l==r) return mi[o]>=v?l:-1;
		if (mi[rc]<v) return find1(rs,x,v);
		int t = find1(ls,x,v);
		return t==-1?mid+1:t;
	}
	if (mid>=x) return find1(ls,x,v);
	int R = find1(rs,x,v);
	if (R==-1||R>mid+1) return R;
	int L = find1(ls,x,v);
	return L==-1?R:L;
}
//求找最大的位置p, 使得[x,p]的最小值>=v
int find2(int o, int l, int r, int x, int v) {
	if (x<=l) {
		if (l==r) return mi[o]>=v?l:-1;
		if (mi[lc]<v) return find2(ls,x,v);
		int t = find2(rs,x,v);
		return t==-1?mid:t;
	}
	if (mid<x) return find2(rs,x,v);
	int L = find2(ls,x,v);
	if (L==-1||L<mid) return L;
	int R = find2(rs,x,v);
	return R==-1?L:R;
}
int query(int u, int v, int l, int r, int k) {
	if (l==r) return l;
	int s = tr[tr[v].l].v-tr[tr[u].l].v;
	if (s>=k) return query(tr[u].l,tr[v].l,l,mid,k);
	return query(tr[u].r,tr[v].r,mid+1,r,k-s);
}
void add(int &o, int l, int r, int x) {
	tr[++tot]=tr[o],o=tot,++tr[o].v;
	if (l!=r) mid>=x?add(tr[o].l,l,mid,x):add(tr[o].r,mid+1,r,x);
}
int query(int p, int len, int k) {
	int l = p>1?find1(1,2,n,p,len)-1:1;
	int r = p<n?find2(1,2,n,p+1,len):n;
	if (l<0) l = p;
	if (r<0) r = p;
	if (r-l+1>=k) return query(T[l-1],T[r],1,n,k);
	return -1;
}
void build2(int o, int l, int r) {
	if (l==r) return mi[o]=h[l],void();
	build2(ls),build2(rs);
	mi[o]=min(mi[lc],mi[rc]);
}
void brute_force() {
	while (q--) {
		int l, r, k;
		scanf("%d%d%d",&l,&r,&k);
		string g(s+l,s+r+1);
		int pos = -1, cnt = 0;
		REP(i,1,n) if (string(s+i,s+i+r-l+1)==g) {
			if (++cnt==k) {
				pos = i; break;
			}
		}
		printf("%d\n", pos);
	}
}
int main() {
	int t;
	scanf("%d", &t);
	while (t--) {
		scanf("%d%d%s", &n, &q, s+1);
		if (n<=10) {brute_force();continue;}
		REP(i,1,n) a[i]=s[i]-‘a‘+1;
		build(a,n,26);
		build2(1,2,n);
		REP(i,1,n) {
			T[i] = T[i-1];
			add(T[i],1,n,sa[i]);
		}
		while (q--) {
			int l, r, k;
			scanf("%d%d%d", &l, &r, &k);
			int len = r-l+1;
			printf("%d\n", query(rk[l],r-l+1,k));
		}
		REP(i,0,n) T[i]=0;
		while (tot) tr[tot].l=tr[tot].r=tr[tot].v=0,--tot;
	}
}

原文地址:https://www.cnblogs.com/uid001/p/11405505.html

时间: 2024-10-10 04:51:13

K-th occurrence HDU - 6704 (SA, 主席树)的相关文章

hdu 6601 (主席树)

传送门 题意: 给你一个长度为\(n\)的序列,有\(q\)个询问,每个询问给你一个区间\([l,r]\),每次询问问你在区间\([l,r]\)中,能够组成的最大的三角形的周长. 分析: 因为三角形具有两边之和大于第三边的性质,即\(a+b>c\)的性质.而倘若有若干个数都符合条件,则倘若我们将不等号改成等号,这就形成了一个斐波那契数列.而根据斐波那契数列的性质,值域在\([1,a]\)的斐波那契数列最多会有\(log2(a)\)项. 而在这里可以利用这个性质,每个询问我们贪心的去取第\(k\)

hdu 3727(主席树例题)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3727 题意是有4种操作 1.在项链后面插入一个珍珠,保证每一个珍珠都不一样 2.查询第 l 到 第 r 个珍珠之间第k大的珍珠的大小 3.假设把所有珍珠按照大小排序,查询size为x的珍珠的排名 4.查询所有珍珠里第k大的珍珠的大小 题目只需要输出2,3,4询问的所有答案即可 第3个询问很简单,不谈.第4个询问本质上和上一道CWOJ的题是一样的.而第2个操作需要用到主席树的思想. 代码大致思路 建立

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

【10.10校内测试】【线段树维护第k小+删除】【lca+主席树维护前驱后驱】

贪心思想.将a排序后,对于每一个a,找到对应的删除m个后最小的b,每次更新答案即可. 如何删除才是合法并且最优的?首先,对于排了序的a,第$i$个那么之前就应该删除前$i-1$个a对应的b.剩下$m-i+1$可以删,那么在剩下的b中查找第$m-i+2$小即可.每次做完就删除当前a对应的b. 注意离散化. 还有数组不要开大了.... #include<bits/stdc++.h> #define LL long long using namespace std; void read(int &a

2019CCPC网络预选赛 1003 K-th occurrence 后缀自动机 + 二分 + 主席树

题意:给你一个长度为n的字符串,有m次询问,每次询问l到r的子串在原串中第k次出现的位置,如果没有输出-1.n, m均为1e5级别. 思路:后悔没学后缀数组QAQ,其实只要学过后缀数组这个题还是比较好想的.这个问题可以转化为有多少个后缀和后缀l的lcp长度大于等于r - l + 1.我们知道,在后缀数组中,两个后缀i, j的lcp是min(height[rank[j] + 1], height[rank[j] + 2], ....height[rank[i]]).那么,我们可以二分出一个最靠左的

bzoj 4504: K个串【大根堆+主席树】

像超级钢琴一样把五元组放进大根堆,每次取一个出来拆开,(d,l,r,p,v)表示右端点为d,左端点区间为(l,r),最大区间和值为v左端点在p上 关于怎么快速求区间和,用可持久化线段树维护(主席树?)每个点到他root的区间和,这样每次右端点右移就是上一个的线段树在(la[a[i]]+1,i)加上a[i],la是这个值a[i]上一次出现的位置 然后就可以在线处理询问了 有一点因为这个线段树建的是1~n,所以右端点不是n的时候取max会取到右端点向右还是初始值0的位置(有可能前面是负数),这样的解

poj 2104主席树求区间第k小

POJ - 2104 题意:求区间第k小 思路:无修改主席树 AC代码: #include "iostream" #include "iomanip" #include "string.h" #include "stack" #include "queue" #include "string" #include "vector" #include "set&

spoj COT - Count on a tree (树上第K小 LCA+主席树)

链接: https://www.spoj.com/problems/COT/en/ 思路: 首先看到求两点之前的第k小很容易想到用主席树去写,但是主席树处理的是线性结构,而这道题要求的是树形结构,我们可以用dfs跑出所有点离根的距离-dep[i](根为1,dep[1]也为1)在dfs的过程 中,我们对每一个节点建一棵线段树,那么[a,b]就是:root[a] + root[b] - root[lca(a,b)] - root[f[lca(a,b)]]; (因为a-b的路径上的权值还要算上lca(

主席树|求区间第k小模板

主席树 学了主席树,用来求区间上的第k小 写一下自己整理后的模板 求区间第k小 #include<bits/stdc++.h> using namespace std; //求区间第k小 const int maxn = 500010; struct node{ int v,lc,rc; }T[maxn * 21]; int n,m; int root[maxn]; int e; void insert(int pre,int cur,int pos,int l,int r){ if(l ==