zoukankan      html  css  js  c++  java
  • LOJ2720. 「NOI2018」你的名字

    给出一个字符串(S),然后有若干次询问,每次统计字符串(T)中出现过的(S_{l..r})中没有出现过的子串的个数。

    (|S|,|T|le 5*10^5,sum |T|le 10^6)


    自己想出来的做法:(O(nlg ^2 n)),直接搞出SAM之后线段树+树上二分跳祖先。具体就是:对(S)建出SAM之后,离线枚举询问右端点。主要问题是对(T)的每个前缀求出作为(S_{l..r})子串的最长后缀,即祖先中深度最大(x)满足(mxr_x-len_x+1ge l)。显然可二分。

    正解:(O(nlg n))。详细一点说明(有些部分上面的做法也需要用到):

    同样是对(S)建出SAM,也是要对(T)的每个前缀求出作为(S_{l..r})子串的最长后缀。求出来之后去重即可(对(T)建SAM,建出(T)的fail树,打标记表示其祖先不计入答案)。

    对于这个问题,如果询问(S_{1..|S|})就可以直接在SAM上跑。现在限制了范围,魔改一下跑的方式:(假设当前点为(p),匹配的长度为(len),新增的字符为(c)

    原来:如果存在(p.trans(c))(p)跳过去且(lenleftarrow len+1),否则跳(fail),且(lenleftarrow p.fail.len)。(细致一些:否则(lenleftarrow len-1),如果(len=p.fail)(p)(fail)。时间复杂度不变。便于推广)

    魔改:处理出每个点的(right)集合。定义(p.query(l,r))表示是否(exist xin p.right,xin [l,r])。如果(p.trans(c).query(l+len,r))为真,则跳过去且(lenleftarrow len+1);否则(lenleftarrow len-1),如果(len=p.fail)则跳(fail)

    可以如此说明它的正确性:当前已经配了合法的(len)长度,现在配(len+1),显然(p.trans(c).query(l+len,r))为必要条件,并且根据(right)集合的定义得知这也是充分的。所以这是充分必要条件。

    处理(right)集合可以用可持久化线段树合并。


    using namespace std;
    #include <bits/stdc++.h>
    #define N 500005
    #define ll long long
    int n,m;
    char s[N],t[N];
    struct SAM{
    	struct Node{
    		Node *c[26],*fa;
    		int len;
    	} d[N*2],*S,*T;
    	int id(Node *x){return x-d;}
    	int cnt;
    	Node *newnode(){
    		++cnt;
    		memset(&d[cnt],0,sizeof d[cnt]);
    		return &d[cnt];
    	}
    	void init(){
    		T=S=&d[cnt=1];
    		memset(&d[1],0,sizeof d[1]);
    	}
    	void insert(int ch){
    		Node *nw=newnode(),*p,*q;
    		nw->len=T->len+1;
    		for (p=T;p && !p->c[ch];p=p->fa)
    			p->c[ch]=nw;
    		if (!p)
    			nw->fa=S;
    		else{
    			q=p->c[ch];
    			if (p->len+1==q->len)
    				nw->fa=q;
    			else{
    				Node *clone=newnode();
    				memcpy(clone,q,sizeof *q);
    				clone->len=p->len+1;
    				for (;p && p->c[ch]==q;p=p->fa)
    					p->c[ch]=clone;
    				nw->fa=q->fa=clone;
    			}
    		}
    		T=nw;
    	}
    	void build(char *s){
    		init();
    		for (;*s;++s)
    			insert(*s-'a');
    	}
    } S,T;
    struct Seg{
    	Seg *l,*r;
    } d[N*50],*null,*rt[N*2];
    int cnt;
    Seg *newnode(){return &(d[++cnt]={null,null});}
    void insert(int x,Seg *&t,int l=1,int r=n){
    	if (t==null) t=newnode();
    	if (l==r) return;
    	int mid=l+r>>1;
    	if (x<=mid) insert(x,t->l,l,mid);
    	else insert(x,t->r,mid+1,r);		
    }
    Seg *merge(Seg *a,Seg *b){
    	if (a==null) return b;
    	if (b==null) return a;
    	Seg *c=newnode();
    	c->l=merge(a->l,b->l);
    	c->r=merge(a->r,b->r);
    	return c;
    }
    bool query(int st,int en,Seg *t,int l=1,int r=n){
    	if (t==null) return 0;
    	if (st<=l && r<=en) return 1;
    	int mid=l+r>>1;
    	bool res=0;
    	if (st<=mid) res=query(st,en,t->l,l,mid);
    	if (mid<en && !res) res=query(st,en,t->r,mid+1,r);
    	return res;
    }
    struct EDGE{
    	int to;
    	EDGE *las;
    } e[N*2];
    int ne;
    EDGE *last[N*2];
    void link(int u,int v){
    	e[ne]={v,last[u]};
    	last[u]=e+ne++;
    }
    void dfsS(int x){
    	for (EDGE *ei=last[x];ei;ei=ei->las){
    		dfsS(ei->to);
    		rt[x]=merge(rt[x],rt[ei->to]);
    	}
    }
    void buildG(){
    	S.build(s+1);
    	null=d;
    	*null={null,null};
    	for (int i=1;i<=S.cnt;++i)
    		rt[i]=null;
    	SAM::Node *t=S.S;
    	for (int i=1;i<=n;++i){
    		t=t->c[s[i]-'a'];
    		insert(i,rt[S.id(t)]);
    	}
    	for (int i=2;i<=S.cnt;++i)
    		link(S.id(S.d[i].fa),i);
    	dfsS(1);
    }
    int bz[N*2],dep[N*2];
    void buildT(){
    	T.build(t+1);
    	memset(last,0,sizeof(EDGE*)*(T.cnt+1));
    	ne=0;
    	for (int i=2;i<=T.cnt;++i){
    		link(T.id(T.d[i].fa),i);
    		dep[i]=T.d[i].len;
    		bz[i]=0;
    	}
    	bz[1]=0;
    }
    ll ans;
    void dfsT(int x){
    	for (EDGE *ei=last[x];ei;ei=ei->las){
    		dfsT(ei->to);
    		bz[x]=max(bz[x],bz[ei->to]);
    		ans+=max(dep[ei->to]-max(bz[ei->to],dep[x]),0);
    	}
    }
    int main(){
    	freopen("name.in","r",stdin);
    	freopen("name.out","w",stdout);
    //	freopen("in.txt","r",stdin);
    //	freopen("out.txt","w",stdout);
    	scanf("%s",s+1);
    	n=strlen(s+1);
    	buildG();
    	int Q;
    	scanf("%d",&Q);
    	while (Q--){
    		int l,r;
    		scanf("%s%d%d",t+1,&l,&r);
    		m=strlen(t+1);
    		buildT();
    		SAM::Node *q=S.S,*p=T.S;
    		int len=0;
    		for (int i=1;i<=m;++i){
    			while (len && (!q->c[t[i]-'a'] || l+len>r || !query(l+len,r,rt[S.id(q->c[t[i]-'a'])]))){
    				--len;
    				if (len==q->fa->len)
    					q=q->fa;
    			}
    			if (q->c[t[i]-'a'] && query(l+len,r,rt[S.id(q->c[t[i]-'a'])]))
    				q=q->c[t[i]-'a'],len++;
    			p=p->c[t[i]-'a'];
    			bz[T.id(p)]=max(bz[T.id(p)],len);
    		}
    		ans=0;
    		dfsT(1);
    		printf("%lld
    ",ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    初识react hooks
    react初识生命周期
    在调用setState之后发生了什么
    课后作业四
    课后作业2
    课后作业1
    自我介绍
    电脑软件推荐
    数据结构
    数组(一维数组)
  • 原文地址:https://www.cnblogs.com/jz-597/p/14264299.html
Copyright © 2011-2022 走看看