zoukankan      html  css  js  c++  java
  • Hdu 6704 K-th occurrence【SAM+线段树合并+树上倍增】

    传送门
    之前用后缀数组+主席树写过:之前的版本
    学习了后缀自动机和线段树合并之后再来做一下这道题。
    首先对字符串 (s) 建后缀自动机,记录下每个前缀的 endpos 和前缀在 SAM 上所处的位置。
    因为 (s) 的子串唯一对应 SAM 上的某一点,这个询问相当于问 parent 树上某一点的子树中第 k 大的 endpos 是多少。
    考虑怎么快速确定询问串对应 SAM 上的哪一点,根据 parent 树的性质,(s) 的前缀对应 parent 树的叶节点,因为已知前缀 (s_{1,r}) 所处位置 (x),所以让 (x) 不断向上跳,直到 (len[fa[x]]<r-l+1le len[x]),此时 (x) 就包含了子串 (s_{l,r}),当然这个过程就用树上倍增就可以了。
    而询问子树上第 k 大值,可以用线段树合并,也可以用 dfs 序将树拉成区间然后建主席树。
    所以后缀自动机就是比后缀数组更灵活,既可以套树算法,也可以拉成区间套区间算法。

    #include <bits/stdc++.h>
    using namespace std;
    const int N=1e5+10;
    int n,m;
    char s[N];
    
    struct SegTrees{
    	#define mid (l+r>>1)
    	int sum[N*50],ls[N*50],rs[N*50],tot;
    	void clear(){
    		memset(sum,0,sizeof(sum));
    		memset(ls,0,sizeof(ls));
    		memset(rs,0,sizeof(rs));
    		tot=0;
    	}
    	void upd(int &id,int l,int r,int pos){
    		if(!id) id=++tot;
    		if(l==r) {sum[id]++;return;}
    		if(pos<=mid) upd(ls[id],l,mid,pos);
    		else upd(rs[id],mid+1,r,pos);
    		sum[id]=sum[ls[id]]+sum[rs[id]];
    	}
    	int merge(int x,int y,int l,int r){
    		if(!x||!y) return x+y;
    		int id=++tot;
    		if(l==r) {sum[id]=sum[x]+sum[y];return id;}
    		ls[id]=merge(ls[x],ls[y],l,mid);
    		rs[id]=merge(rs[x],rs[y],mid+1,r);
    		sum[id]=sum[ls[id]]+sum[rs[id]];
    		return id;
    	}
    	int ask(int id,int l,int r,int k){
    		if(l==r) return l;
    		if(k<=sum[ls[id]]) return ask(ls[id],l,mid,k);
    		else return ask(rs[id],mid+1,r,k-sum[ls[id]]);
    	}
    	#undef mid
    }trs;
    
    struct SuffixAutoMachine{
    	int last,tot,len[N*2],fa[N*2],ch[N*2][26],ep[N*2],tpos[N],st[N*2][19];
    	int head[N*2],to[N*2],nxt[N*2],rt[N*2],total;
    	void add(int u,int v){to[++total]=v;nxt[total]=head[u];head[u]=total;}
    	int newnode(int x){fa[++tot]=fa[x];len[tot]=len[x];memcpy(ch[tot],ch[x],sizeof(ch[tot]));return tot;}
    	void append(int c,int pos){
    		int p=last,np=last=newnode(0);
    		len[np]=len[p]+1;ep[np]=pos;tpos[pos]=np;
    		for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
    		if(!p) {fa[np]=1;return;}
    		int q=ch[p][c];
    		if(len[q]==len[p]+1) {fa[np]=q;return;}
    		int nq=newnode(q);ep[nq]=0;len[nq]=len[p]+1;
    		fa[q]=fa[np]=nq;
    		for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
    	}
    	void dfs(int u){
    		if(ep[u]) trs.upd(rt[u],1,n,ep[u]);
    		for(int i=head[u];i;i=nxt[i]){
    			dfs(to[i]);
    			rt[u]=trs.merge(rt[u],rt[to[i]],1,n);
    		}
    	}
    	void init(){
    		last=tot=total=0;
    		last=tot=newnode(0);
    		memset(head,0,sizeof(head));
    		memset(rt,0,sizeof(rt));
    		trs.clear();
    		for(int i=1;i<=n;i++) append(s[i]-'a',i);
    		for(int i=2;i<=tot;i++) add(fa[i],i),st[i][0]=fa[i];
    		dfs(1);
    		for(int i=1;i<19;i++) for(int j=1;j<=tot;j++) st[j][i]=st[st[j][i-1]][i-1];
    	}
    	void ask(int l,int r,int k){
    		int p=tpos[r];
    		for(int i=18;i>=0;i--) if(len[st[p][i]]>=r-l+1) p=st[p][i];
    		if(trs.sum[rt[p]]<k) {printf("-1
    ");return;}
    		printf("%d
    ",trs.ask(rt[p],1,n,k)-(r-l));
    	}
    }sam;
    
    void solve(){
    	scanf("%d%d",&n,&m);
    	scanf("%s",s+1);
    	sam.init();
    	for(int i=1,l,r,k;i<=m;i++){
    		scanf("%d%d%d",&l,&r,&k);
    		sam.ask(l,r,k);
    	}
    }
    
    int main(){
    	int T;scanf("%d",&T);
    	while(T--) solve();
    	return 0;
    }
    
  • 相关阅读:
    spring(2)
    Android之滑动按钮实现Demo
    spring(1)
    spring(4)
    Android之ImageSwitch控件
    使用非阻塞ServerSocketChannel、SocketChannel代替ServerSocket和Socket
    Android之界面刷新(invalidate和postInvalidate使用)
    Android之改变控件的背景及形态
    Android之获得内存剩余大小与总大小
    OpenCV中矩阵的归一化
  • 原文地址:https://www.cnblogs.com/BakaCirno/p/12679997.html
Copyright © 2011-2022 走看看