zoukankan      html  css  js  c++  java
  • [NOI2018]你的名字(后缀自动机+线段树合并)

    [NOI2018]你的名字(后缀自动机+线段树合并)

    题面

    给出一个字符串(S),有(q)组询问,每次询问给出一个字符串(T)和整数(l,r).问能从(T)中选出多少个本质不同的子串,满足这个子串在(S)的区间([l,r])没有出现过。

    (|S| leq 5 imes 10^5,q leq 10^5,sum|T| leq 5 imes 10^5)

    分析

    AC这道题的一瞬间,我感觉和后缀自动姬合二为一,达到了生命的大和谐。

    简化问题

    考虑一个简化的问题,只考虑(T)和整个(S)串的答案。
    (S)(T)都建出后缀自动机,记为(M_1)(M_2).然后把(T)串放到(M_1)上匹配,记录当前节点(x_1)和匹配长度(matlen),然后还要记录在(M_2)上走到的节点(x_2). 对于节点(x_2),它贡献的本质不同的串的长度应该在([len(link(x_2))+1,len(x_2)]),个数就是区间长度. 但是还要考虑到如果不能与(S)匹配,所以如果(matlen)落在([len(link(x_2))+1,len(x_2)]),贡献的本质的长度范围应该是([matlen+1,len(x_2)]).

    因此我们对(M_2)的每个节点维护一个值(mlen)表示该节点与(S)的最大匹配长度。每次用matlen去更新答案。更新方法是沿着link树往上跳,设跳到的节点为(y),如果(matlen<len(link(y)+1)),那么mlen不变,增加的子串个数为(mlen-len(link(y))).否则更新(mlen)(matlen),增加的子串个数为(matlen-len(link(y))).

    正解

    如何求区间(S[l,r])内的字符与(T)匹配的结果呢?我们可以用到(right)集合。如果(M_1)(right)集合中存在落在当前需要匹配的区间([l+matlen,r])中,那么匹配长度(matlen)就可以+1. 否则也是沿着(link)往上跳即可。(right)集合可以用权值线段树维护,预处理的时候用线段树合并,查询的时候就是一个区间求和。

    细节比较多,见代码注释

    代码

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    #define maxc 26
    #define maxn 1000000
    #define maxlogn 30
    using namespace std;
    int n,q;
    char s[maxn+5],t[maxn+5];
    
    struct segment_tree{
    #define lson(x) (tree[x].ls)
    #define rson(x) (tree[x].rs)
    	struct node{
    		int ls;
    		int rs;
    		int val;
    	}tree[maxn*maxlogn+5];
    	int ptr=0;
    	void push_up(int x){
    		tree[x].val=tree[lson(x)].val+tree[rson(x)].val;
    	}
    	void update(int &x,int upos,int l,int r){
    		x=++ptr;
    		if(l==r){
    			tree[x].val++;
    			return;
    		}
    		int mid=(l+r)>>1;
    		if(upos<=mid) update(lson(x),upos,l,mid);
    		else update(rson(x),upos,mid+1,r);
    		push_up(x);
    	}
    	int query(int x,int L,int R,int l,int r){
    		if(x==0||L>R) return 0;
    		if(L<=l&&R>=r) return tree[x].val;
    		int mid=(l+r)>>1;
    		int ans=0;
    		if(L<=mid) ans+=query(lson(x),L,R,l,mid);
    		if(R>mid) ans+=query(rson(x),L,R,mid+1,r);
    		return ans; 
    	}
    	int merge(int x,int y){
    		if(!x||!y) return x+y;	
    		int now=++ptr;//这样合并不会影响原来的两棵树,类似可持久化
    		tree[now].val=tree[x].val+tree[y].val;
    		lson(now)=merge(lson(x),lson(y));
    		rson(now)=merge(rson(x),rson(y));
    		return now; 
    	} 
    #undef lson
    #undef rson
    }Tr;
    
    struct SAM{
    #define link(x) t[x].link 
    #define len(x) t[x].len
    	struct node{
    		int ch[maxc];
    		int link;
    		int len;
    		int mlen;//该位置代表的后缀向前最长的合法长度(没有和S匹配) 
    		int root;//代表的right集合对应的线段树的根节点 
    		void clear(){
    			//因为要多次重建自动机,做好初始化 
    			for(int i=0;i<maxc;i++) ch[i]=0;
    			link=len=root=0;
    		}
    	}t[maxn*2+5];
    	const int root=1;
    	int last=root;
    	int ptr=1;
    	inline int size(){
    		return ptr;
    	}
    	void ini(){
    		last=root;
    		ptr=1;
    		t[root].clear();
    	}
    	int New(){
    		ptr++;
    		t[ptr].clear();
    		return ptr;
    	}
    	inline int delta(int x,int c){
    		return t[x].ch[c];//转移函数 
    	}
    	void extend(int c,int pos){
    		int p=last,cur=New();
    		len(cur)=len(p)+1;
    		if(pos) Tr.update(t[cur].root,pos,1,n);//因为并不需要T的right集合,插入T的时候把pos设为0 
    		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=New();
    				len(clo)=len(p)+1;
    				for(int i=0;i<maxc;i++) t[clo].ch[i]=t[q].ch[i];
    				link(clo)=link(q);
    				link(q)=link(cur)=clo;
    				while(p&&t[p].ch[c]==q){
    					t[p].ch[c]=clo;
    					p=link(p);
    				}
    			}
    		} 
    		last=cur;
    	}
    	void topo_sort(){
    		static int in[maxn+5];
    		queue<int>q;
    		for(int i=1;i<=ptr;i++) in[link(i)]++;
    		for(int i=1;i<=ptr;i++) if(!in[i]) q.push(i);
    		while(!q.empty()){
    			int x=q.front();
    			q.pop();
    			if(link(x)==0) continue;
    			in[link(x)]--;
    			t[link(x)].root=Tr.merge(t[link(x)].root,t[x].root);
    			if(in[link(x)]==0) q.push(link(x));
    		}
    	}  
    }S1,S2;
    long long calc(char *t,int l,int r){
    	int lent=strlen(t+1);
    	S2.ini();
    	for(int i=1;i<=lent;i++) S2.extend(t[i]-'a',0);
    	for(int i=1;i<=S2.size();i++) S2.t[i].mlen=S2.t[i].len;
    	int x1=S1.root,x2=S2.root;
    	int matlen=0;//S[l,r]与T的匹配长度 
    	long long ans=0;
    	for(int i=1;i<=lent;i++){
    		int c=t[i]-'a';
    		x2=S2.delta(x2,c);
    		if(S1.delta(x1,c)&&Tr.query(S1.t[S1.delta(x1,c)].root,l+matlen,r,1,n)){
    			//如果S串的ch[x][c]对应的right集合中,有被当前需要匹配的范围[l+tot,r]包含的,就说明当前的T与S[l,r]匹配长度+1. 
    			x1=S1.t[x1].ch[c];
    			matlen++;
    		}else{
    			while(x1&&!(S1.delta(x1,c)&&Tr.query(S1.t[S1.delta(x1,c)].root,l+matlen,r,1,n))){//不能匹配,跳link 
    				if(matlen==0){
    					x1=0;
    					break;
    				}
    				matlen--;//减少匹配长度,尝试卡进区间内
    				if(matlen==S1.len(S1.link(x1))) x1=S1.link(x1);
    			}
    			if(!x1){
    				x1=S1.root;
    				matlen=0;
    			}else{
    				x1=S1.delta(x1,c);
    				matlen++;
    			}
    		}
    		for(int y=x2;y;y=S2.link(y)){
    			if(matlen>S2.len(S2.link(y))){//匹配位置在当前节点代表的串[len(link(x))+1,len(x)]内 
    				ans+=S2.t[y].mlen-matlen;
    				S2.t[y].mlen=matlen;
    				break;//此时对祖先节点已经没有影响,可以break 
    			}else{//否则这整个区间都是合法的 
    				ans+=S2.t[y].mlen-S2.len(S2.link(y));
    				S2.t[y].mlen=S2.len(S2.link(y));//这一部分的贡献已经计算过了,要移到前面 
    			} 
    		}
    //		printf("i=%d x1=%d x2=%d,ans=%lld
    ",i,x1,x2,ans);
    	}
    //	printf("----
    ");
     	return ans;
    }
    
    
    int main(){
    #ifndef LOCAL
    	freopen("name.in","r",stdin);
    	freopen("name.out","w",stdout);
    #endif
    	int l,r;
    	scanf("%s",s+1);
    	n=strlen(s+1);
    	S1.ini();
    	for(int i=1;i<=n;i++) S1.extend(s[i]-'a',i);
    	S1.topo_sort();
    	scanf("%d",&q);
    	for(int i=1;i<=q;i++){
    		scanf("%s",t+1);
    		scanf("%d %d",&l,&r);
    		printf("%lld
    ",calc(t,l,r));
    	}
    }
    /*
    abc
    1
    cad 2 3
    */
    
  • 相关阅读:
    Xcode编译报错信息总结
    iOS组件化方案
    xcodebuild命令
    Mac下配置MAMP Pro+PHPStorm
    Sublime Text PHP Mac系统环境配置
    JS生成二维码
    为IE和chrome编写单独的样式
    几个简单的VBS脚本程序以及其JS实现
    vue组件中使用iframe元素
    nginx简易部署
  • 原文地址:https://www.cnblogs.com/birchtree/p/12416613.html
Copyright © 2011-2022 走看看