• [BZOJ3879]SvT(后缀树+虚树)


    [BZOJ3879]SvT(后缀树+虚树)

    题面

    有一个长度为n的仅包含小写字母的字符串S,下标范围为[1,n].
    现在有若干组询问,对于每一个询问,我们给出若干个后缀(以其在S中出现的起始位置来表示),求这些后缀两两之间的LCP的长度之和.一对后缀之间的LCP长度仅统计一遍.

    分析

    建出S的后缀树(实现上直接建反串SAM的parent树). 每一组询问的后缀相当于后缀树上的一个点集。两个后缀在后缀树上对应的节点LCA的len就是他们的LCP长度。因此我们要查询这些点两两之间LCA的len之和。

    把这些关键点建出虚树。然后在虚树上做类似树形dp的统计。设(sz_x)表示(x)当前已经合并的子树中关键点的个数,则对于还未被合并的儿子(y),它和已经合并的子树会产生(sz_x cdot sz_y)对点,而这些点对的LCA都是(x)。因此对答案的贡献为(sz_x cdot sz_y cdot len(x))

    代码

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<vector>
    #include<algorithm>
    #define maxc 26
    #define maxn 3000000
    #define maxm 3000000
    #define mod 23333333333333333ll
    using namespace std;
    template<typename T> void qread(T &x){
    	x=0;
    	T sign=1;
    	char c=getchar();
    	while(c<'0'||c>'9'){
    		if(c=='-') sign=-1;
    		c=getchar();
    	}
    	while(c>='0'&&c<='9'){
    		x=x*10+c-'0';
    		c=getchar();
    	}
    	x=x*sign; 
    } 
    template<typename T> void qprint(T x){
    	if(x<0){
    		putchar('-');
    		qprint(-x);
    	}else if(x==0){
    		putchar('0');
    		return;
    	}else{
    		if(x>=10) qprint(x/10);
    		putchar('0'+x%10);
    	}
    }
    
    int n,m;
    char str[maxn+5];
    int pos[maxn+5];
    vector<int>E[maxn+5];
    
    int qu,q[maxm+5];
    
    struct SAM {
    #define len(x) (t[x].len)
    #define link(x) (t[x].link)
    	struct node {
    		int ch[maxc];
    		int link;
    		int len;
    	} t[maxn+5];
    	const int root=1;
    	int ptr=1;
    	int last=root;
    	int extend(int c) {
    		int p=last,cur=++ptr;
    		len(cur)=len(p)+1;
    		while(p&&t[p].ch[c]==0) {
    			t[p].ch[c]=cur;
    			p=link(p);
    		}
    		if(p==0) link(cur)=root;
    		else {
    			int q=t[p].ch[c];
    			if(len(p)+1==len(q)) link(cur)=q;
    			else {
    				int clo=++ptr;
    				t[clo]=t[q];
    				len(clo)=len(p)+1;
    				link(q)=link(cur)=clo;
    				while(p&&t[p].ch[c]==q) {
    					t[p].ch[c]=clo;
    					p=link(p);
    				}
    			}
    		}
    		last=cur;
    		return cur;
    	}
    	void build(char *s){
    		int len=strlen(s+1);
    		for(int i=len;i>=1;i--) pos[i]=extend(s[i]-'a');
    		for(int i=1;i<=ptr;i++) E[link(i)].push_back(i);
    #ifdef DEBUG
    		puts("tree:");
    		for(int i=1;i<=ptr;i++) printf("%d %d
    ",link(i),i);
    		printf("pos:"); 
    		for(int i=1;i<=len;i++) printf("%d ",pos[i]);
    		printf("
    ");
    #endif
    	}
    }S;
    
    int tim;
    int dfn[maxm+5],deep[maxm+5],top[maxm+5],son[maxm+5],fa[maxm+5],sz[maxm+5];
    void dfs1(int x,int f){
    	sz[x]=1;
    	fa[x]=f;
    	deep[x]=deep[f]+1;
    	for(int i=0;i<(int)E[x].size();i++){
    		int y=E[x][i];
    		if(y!=f){
    			dfs1(y,x);
    			sz[x]+=sz[y];
    			if(sz[y]>sz[son[x]]) son[x]=y;
    		}
    	}
    }
    void dfs2(int x,int t){
    	dfn[x]=++tim;
    	top[x]=t;
    	if(son[x]) dfs2(son[x],t);
    	for(int i=0;i<(int)E[x].size();i++){
    		int y=E[x][i];
    		if(y!=fa[x]&&y!=son[x]) dfs2(y,y); 
    	}
    }
    int lca(int x,int y){
    	while(top[x]!=top[y]){
    		if(deep[top[x]]<deep[top[y]]) swap(x,y);
    		x=fa[top[x]];
    	}
    	return deep[x]<deep[y]?x:y;
    }
    
    vector<int>vt[maxm+5];//虚树
    bool is_orig[maxm+5];//虚树中的关键点(不含lca) 
    int stk[maxm+5],_top; 
    inline int cmp(int x,int y){
    	return dfn[x]<dfn[y];
    }
    void vt_insert(int x){
    	if(_top<1){
    		stk[++_top]=x;
    		return;
    	}
    	int lc=lca(x,stk[_top]);
    	if(stk[_top]==lc){
    		stk[++_top]=x;
    		return;
    	}
    	while(_top>1&&deep[stk[_top-1]]>=deep[lc]){
    		vt[stk[_top-1]].push_back(stk[_top]);
    		_top--;
    	}
    	if(stk[_top]!=lc) vt[lc].push_back(stk[_top]);
    	stk[_top]=lc;
    	stk[++_top]=x;
    } 
    long long ans=0;
    int dfs3(int x,int f){
    	int szx=0;//子树中关键点个数,只开一个变量,这样不用清空
    	if(is_orig[x]) szx++; 
    	for(int i=0;i<(int)vt[x].size();i++){
    		int y=vt[x][i];
    		if(y!=f){
    			int szy=dfs3(y,x);
    			ans=(ans+1ll*szx*szy*S.t[x].len%mod)%mod;
    			szx+=szy;
    		}
    		
    	}
    	vt[x].clear(); 
    	return szx;
    }
    void get_tree(int *p,int cnt){
    	sort(p+1,p+1+cnt,cmp);
    	cnt=unique(p+1,p+1+cnt)-p-1;//题目说可能有重复后缀 
    	_top=0;
    	for(int i=1;i<=cnt;i++) is_orig[p[i]]=1; 
    	int root=0;
    	for(int i=1;i<=cnt;i++){
    		if(!root) root=p[i];
    		else root=lca(root,p[i]);
    	}
    	for(int i=1;i<=cnt;i++) vt_insert(p[i]);
    	while(_top>1){
    		vt[stk[_top-1]].push_back(stk[_top]);
    		_top--;
    	}
    	ans=0;
    	dfs3(root,0);
    	for(int i=1;i<=cnt;i++) is_orig[p[i]]=0; 
    }
    int main(){
    	qread(n);
    	qread(m);
    	scanf("%s",str+1);
    	S.build(str);
    	dfs1(1,0);
    	dfs2(1,1); 
    	for(int i=1;i<=m;i++){
    		qread(qu);
    		for(int j=1;j<=qu;j++){
    			qread(q[j]);
    			q[j]=pos[q[j]];
    		}
    		get_tree(q,qu);
    		qprint(ans);
    		putchar('
    ');
    	}
    }
    /*
    4 3
    aaaa
    2 3 4
    */ 
    
  • 相关阅读:
    循环链表问题
    非常有用的编程学习网站
    我的单例模式(C++)
    C# xml解析
    设计模式趣解
    简单工厂(C++)
    贝塞尔曲线 原理
    C++ 1.#QNAN0;1.#QNAN0
    [NOI2018]屠龙勇士 excrt
    [NOI.AC#30]candy 贪心
  • 原文地址:https://www.cnblogs.com/birchtree/p/12594319.html
Copyright © 2020-2023  润新知