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[N],buf1[N],buf2[N],c[N],n,rnk[N],ht[N],ST[N][20],Log[N],m;
 7 void Sort(int* x,int* y,int m) {
 8     for(int i=0; i<m; ++i)c[i]=0;
 9     for(int i=0; i<n; ++i)++c[x[i]];
10     for(int i=1; i<m; ++i)c[i]+=c[i-1];
11     for(int i=n-1; i>=0; --i)sa[--c[x[y[i]]]]=y[i];
12 }
13 void da(int* s,int n,int m=1000) {
14     int *x=buf1,*y=buf2;
15     x[n]=y[n]=-1;
16     for(int i=0; i<n; ++i)x[i]=s[i],y[i]=i;
17     Sort(x,y,m);
18     for(int k=1; k<n; k<<=1) {
19         int p=0;
20         for(int i=n-k; i<n; ++i)y[p++]=i;
21         for(int i=0; i<n; ++i)if(sa[i]>=k)y[p++]=sa[i]-k;
22         Sort(x,y,m),p=1,y[sa[0]]=0;
23         for(int i=1; i<n; ++i)y[sa[i]]=x[sa[i-1]]==x[sa[i]]&&x[sa[i-1]+k]==x[sa[i]+k]?p-1:p++;
24         if(p==n)break;
25         swap(x,y),m=p;
26     }
27 }
28 void getht() {
29     for(int i=0; i<n; ++i)rnk[sa[i]]=i;
30     ht[0]=0;
31     for(int i=0,k=0; i<n; ++i) {
32         if(k)--k;
33         if(!rnk[i])continue;
34         for(; s[i+k]==s[sa[rnk[i]-1]+k]; ++k);
35         ht[rnk[i]]=k;
36     }
37 }
38 void initST() {
39     for(int i=1; i<n; ++i)ST[i][0]=ht[i];
40     for(int j=1; (1<<j)<=n; ++j)
41         for(int i=1; i+(1<<j)-1<n; ++i)
42             ST[i][j]=min(ST[i][j-1],ST[i+(1<<(j-1))][j-1]);
43 }
44 int lcp(int l,int r) {
45     if(l==r)return n-sa[l];
46     if(l>r)swap(l,r);
47     l++;
48     int k=Log[r-l+1];
49     return min(ST[l][k],ST[r-(1<<k)+1][k]);
50 }
51 int rt[N],ls[N*30],rs[N*30],sum[N*30],tot;
52 #define mid ((l+r)>>1)
53 int cpy(int v) {int u=++tot; ls[u]=ls[v],rs[u]=rs[v],sum[u]=sum[v]; return u;}
54 void upd(int& u,int v,int p,int l=0,int r=n-1) {
55     u=cpy(v);
56     sum[u]++;
57     if(l==r)return;
58     p<=mid?upd(ls[u],ls[v],p,l,mid):upd(rs[u],rs[v],p,mid+1,r);
59 }
60 int qry(int u,int v,int k,int l=0,int r=n-1) {
61     if(l==r)return l;
62     int cnt=sum[ls[u]]-sum[ls[v]];
63     return k<=cnt?qry(ls[u],ls[v],k,l,mid):qry(rs[u],rs[v],k-cnt,mid+1,r);
64 }
65 void build() {
66     da(s,n),getht(),initST();
67     tot=0;
68     for(int i=1; i<=n; ++i)upd(rt[i],rt[i-1],sa[i-1]);
69 }
70 int main() {
71     Log[0]=-1;
72     for(int i=1; i<N; ++i)Log[i]=Log[i>>1]+1;
73     int T;
74     for(scanf("%d",&T); T--;) {
75         scanf("%d%d%s",&n,&m,buf);
76         for(int i=0; i<n; ++i)s[i]=buf[i];
77         s[n]=0;
78         build();
79         while(m--) {
80             int l,r,k;
81             scanf("%d%d%d",&l,&r,&k);
82             l--,r--;
83             int L,R,M=rnk[l],len=r-l+1;
84             L=R=M;
85             for(int i=Log[n]; i>=0; --i)if(L-(1<<i)>=0&&lcp(M,L-(1<<i))>=len)L-=(1<<i);
86             for(int i=Log[n]; i>=0; --i)if(R+(1<<i)<n&&lcp(M,R+(1<<i))>=len)R+=(1<<i);
87             printf("%d\n",k<=sum[rt[R+1]]-sum[rt[L]]?qry(rt[R+1],rt[L],k)+1:-1);
88         }
89     }
90     return 0;
91 }

解法二:

建立后缀自动机,对后缀树(fail树)作线段树合并可得到每个结点包含的全部right值。对每个询问倍增找到待查询子串所对应的结点,然后线段树上查询第k大即可。

可持久化合并可以实现在线查询。

fail树上dfs序建可持久化线段树貌似也可以(这句话怎么这么耳熟?)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 const int N=2e5+10,M=26;
 5 char s[N];
 6 int n,m,fa[N],go[N][M],mxl[N],last,tot,samrt[N],c[N],ss[N],Fa[N][20];
 7 int rt[N],ls[N*50],rs[N*50],sum[N*50],tot2;
 8 #define mid ((l+r)>>1)
 9 int cpy(int v) {int u=++tot2; ls[u]=ls[v],rs[u]=rs[v],sum[u]=sum[v]; return u;}
10 void upd(int& u,int v,int p,int l=1,int r=n) {
11     u=cpy(v),sum[u]++;
12     if(l==r)return;
13     p<=mid?upd(ls[u],ls[v],p,l,mid):upd(rs[u],rs[v],p,mid+1,r);
14 }
15 void mg(int& w,int u,int v) {
16     if(!u||!v) {w=u|v; return;}
17     w=cpy(u),sum[w]+=sum[v];
18     mg(ls[w],ls[u],ls[v]),mg(rs[w],rs[u],rs[v]);
19 }
20 int qry(int u,int k,int l=1,int r=n) {
21     if(l==r)return l;
22     return k<=sum[ls[u]]?qry(ls[u],k,l,mid):qry(rs[u],k-sum[ls[u]],mid+1,r);
23 }
24 int newnode(int l) {int u=++tot; rt[u]=0,mxl[u]=l,memset(go[u],0,sizeof go[u]); return u;}
25 void add(int ch,int r) {
26     int p=last,np=last=newnode(mxl[p]+1);
27     samrt[r]=np;
28     upd(rt[np],rt[np],r,1);
29     for(; p&&!go[p][ch]; p=fa[p])go[p][ch]=np;
30     if(!p)fa[np]=1;
31     else {
32         int q=go[p][ch];
33         if(mxl[q]==mxl[p]+1)fa[np]=q;
34         else {
35             int nq=newnode(mxl[p]+1);
36             memcpy(go[nq],go[q],sizeof go[q]);
37             fa[nq]=fa[q],fa[q]=fa[np]=nq;
38             for(; p&&go[p][ch]==q; p=fa[p])go[p][ch]=nq;
39         }
40     }
41 }
42 void build() {
43     tot=tot2=0,last=newnode(0);
44     for(int i=0; i<n; ++i)add(s[i]-‘a‘,i+1);
45     for(int i=0; i<=tot; ++i)c[i]=0;
46     for(int i=1; i<=tot; ++i)++c[mxl[i]];
47     for(int i=1; i<=tot; ++i)c[i]+=c[i-1];
48     for(int i=1; i<=tot; ++i)ss[--c[mxl[i]]]=i;
49     for(int i=tot-1; i>0; --i)mg(rt[fa[ss[i]]],rt[fa[ss[i]]],rt[ss[i]]);
50     for(int i=1; i<=tot; ++i)Fa[i][0]=fa[i];
51     for(int k=1; k<20; ++k)for(int i=1; i<=tot; ++i)Fa[i][k]=Fa[Fa[i][k-1]][k-1];
52 }
53 int main() {
54     int T;
55     for(scanf("%d",&T); T--;) {
56         scanf("%d%d%s",&n,&m,s);
57         build();
58         while(m--) {
59             int l,r,k;
60             scanf("%d%d%d",&l,&r,&k);
61             int u=samrt[r],len=r-l+1;
62             if(mxl[fa[u]]+1>len) {
63                 for(int k=19; k>=0; --k)if(mxl[fa[Fa[u][k]]]+1>len)u=Fa[u][k];
64                 u=fa[u];
65             }
66             printf("%d\n",k<=sum[rt[u]]?qry(rt[u],k)-len+1:-1);
67         }
68     }
69     return 0;
70 }

原文地址:https://www.cnblogs.com/asdfsag/p/11515433.html

时间: 2024-11-05 23:26:31

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

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

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

主席树/函数式线段树/可持久化线段树

什么是主席树 可持久化数据结构(Persistent data structure)就是利用函数式编程的思想使其支持询问历史版本.同时充分利用它们之间的共同数据来减少时间和空间消耗. 因此可持久化线段树也叫函数式线段树又叫主席树. 可持久化数据结构 在算法执行的过程中,会发现在更新一个动态集合时,需要维护其过去的版本.这样的集合称为是可持久的. 实现持久集合的一种方法时每当该集合被修改时,就将其整个的复制下来,但是这种方法会降低执行速度并占用过多的空间. 考虑一个持久集合S. 如图所示,对集合的

主席树——多棵线段树的集合

主席树: (不要管名字) 我们有的时候,会遇到很多种情况,对于每一种情况,都需要通过线段树的操作实现. 碰巧的是,相邻两种情况下的线段树的差异不大.(总体的差异次数是O(N)级别的,均摊就是O(常数)的了) 显然的是,我们不能对于每种情况都建造一棵线段树.n^n 空间直接MLE无疑. 救命稻草是:发现相邻两种情况下的线段树的差异不大. 所以,我们是否可以让不同的线段树共用同一个节点呢?!?!? 这就是主席树的本质.也是精妙之处所在. 代码实现不是很麻烦. 我一般用传返回值形式,每次返回一个节点编

[bzoj3932][CQOI2015]任务查询系统-题解[主席树][权值线段树]

Description 最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分.超级计算机中的 任务用三元组(Si,Ei,Pi)描述,(Si,Ei,Pi)表示任务从第Si秒开始,在第Ei秒后结束(第Si秒和Ei秒任务也在运行 ),其优先级为Pi.同一时间可能有多个任务同时执行,它们的优先级可能相同,也可能不同.调度系统会经常向 查询系统询问,第Xi秒正在运行的任务中,优先级最小的Ki个任务(即将任务按照优先级从小到大排序后取前Ki个 )的优先级之和是多少.特别的,如

hdu 5029 Relief grain(树链剖分+线段树)

题目链接:hdu 5029 Relief grain 题目大意:给定一棵树,然后每次操作在uv路径上为每一个节点加入一个数w,最后输出每一个节点个数最多的那个数. 解题思路:由于是在树的路径上做操作,所以基本就是树链剖分了.仅仅只是曾经是用一个数组就可以维护值,这题要用 一个vector数组记录.过程中用线段树维护最大值. #pragma comment(linker, "/STACK:1024000000,1024000000") #include <cstdio> #i

主席树 | | 可持久化线段树

可持久化数据结构(Persistent data structure)就是利用函数式编程的思想使其支持询问历史版本.同时充分利用它们之间的共同数据来减少时间和空间消耗. 所以这里讲的可持久化线段树也叫函数式线段树(又叫主席树……因为先驱就是fotile主席Orz……). 先了解一下主席树 http://seter.is-programmer.com/posts/31907.html    很详细的介绍了函数式线段树(主席树). 主席树其实就是很多棵线段树,由于每次更新只需要更新logN个节点,所

HDU 3966 Aragorn&#39;s Story (树链剖分+线段树)

题意:给你一棵树,然后有三种操作 I L R K: 把L与R的路径上的所有点权值加上K D L R K:把L与R的路径上的所有点权值减去K Q X:查询节点编号为X的权值 思路:树链剖分裸题(我还没有怎么学懂,但基本已经没有什么太大的问题,主要的问题就在于点或者边对于数据结构的映射关系是,主要没有单独手写过树链剖分,所以对这部分 没有什么体会) 我们知道树链剖分是一种将树剖为链的一种算法,其思想和dfs序差不多,但根据树链剖分的性质,我们的重链是连续的区间,这样对于重链或者重链上的点我们可以方便

Hdu 3966 Aragorn&#39;s Story (树链剖分 + 线段树区间更新)

题目链接: Hdu 3966 Aragorn's Story 题目描述: 给出一个树,每个节点都有一个权值,有三种操作: 1:( I, i, j, x ) 从i到j的路径上经过的节点全部都加上x: 2:( D, i, j, x ) 从i到j的路径上经过的节点全部都减去x: 3:(Q, x) 查询节点x的权值为多少? 解题思路: 可以用树链剖分对节点进行hash,然后用线段树维护(修改,查询),数据范围比较大,要对线段树进行区间更新 1 #include <cstdio> 2 #include

HDU 2460 Network(双连通+树链剖分+线段树)

HDU 2460 Network 题目链接 题意:给定一个无向图,问每次增加一条边,问个图中还剩多少桥 思路:先双连通缩点,然后形成一棵树,每次增加一条边,相当于询问这两点路径上有多少条边,这个用树链剖分+线段树处理 代码: #include <cstdio> #include <cstring> #include <algorithm> #include <vector> using namespace std; #pragma comment(linke