zoukankan      html  css  js  c++  java
  • [BZOJ5417] [NOI2018]你的名字

    Description

    小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了。

    由于ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手册规定:每年由命题委员会规定一个小写字母字符串,我们称之为那一年的命名串,要求每道题的名字必须是那一年的命名串的一个非空连续子串,且不能和前一年的任何一道题目的名字相同。

    由于一些特殊的原因,小A 不知道ION2017 每道题的名字,但是他通过一些特殊手段得到了ION2017 的命名串,现在小A 有Q 次询问:每次给定ION2017 的命名串和ION2018 的命名串,求有几种题目的命名,使得这个名字一定满足命题委员会的规定,即是ION2018 的命名串的一个非空连续子串且一定不会和ION2017 的任何一道题目的名字相同。

    由于一些特殊原因,所有询问给出的ION2017 的命名串都是某个串的连续子串,详细可见输入格式。

    Input

    第一行一个字符串S ,之后询问给出的ION2017 的命名串都是S 的连续子串。 第二行一个正整数Q,表示询问次数。 接下来Q 行,每行有一个字符串T 和两个正整数l,r,表示询问如果ION2017 的 命名串是S [l..r],ION2018 的命名串是T 的话,有几种命名方式一定满足规定。

    Output

    输出Q 行,第i 行一个非负整数表示第i 个询问的答案。

    Sample Input

    scbamgepe
    3
    smape 2 7
    sbape 3 8
    sgepe 1 9
    

    Sample Output

    12
    10
    4
    

    Solution

    考虑(l=1,r=|S|)怎么做,先对(S)(T)都建出后缀自动机,然后设(lim_i)表示字符串(T)的第(i)位能和(S)匹配到的最长的在(T)串以(i)为最后一个点的后缀是多少,也就是(T[x...i]=S'),其中(S')(S)的任意一个子串,(lim_i=max{i-x+1})

    (pos[i])表示(T)的后缀自动机节点(i)(right)集合最小的那个点。

    那么如果能预处理这些东西,答案很显然就是:

    [ans=sum_{i=2}^{cnt}max(0,maxl_i-max(lim_{pos_i},maxl_{par_i})) ]

    这个式子的意思也很显然,就是考虑(T)(SAM)每个节点的贡献就好了。

    考虑如何算(lim_i),当前情况直接吧串(T)扔到(S)的后缀自动机上跑,然后失配了就跳(parent)就好了。

    那么满分做法就很显然了,对于(S)的自动机上每个点开一个线段树来存(right)集合的分布情况,这个可以线段树合并搞搞,然后对于询问的([l,r])区间,处理(lim_i)的时候,设当前的匹配长度是(len),那么跳(parent)时判断一下有没有一个(right)集合的元素在区间([l+len,r])之内,如果有就匹配上,没有就继续跳。

    答案还是和上面一样算。

    复杂度为(O(|S|log |S|+sum|T|log |S|))

    #include<bits/stdc++.h>
    using namespace std;
     
    void read(int &x) {
        x=0;int f=1;char ch=getchar();
        for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-f;
        for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';x*=f;
    }
    
    #define ll long long 
    
    void print(ll x) {
        if(x<0) putchar('-'),x=-x;
        if(!x) return ;print(x/10),putchar(x%10+48);
    }
    void write(ll x) {if(!x) putchar('0');else print(x);putchar('
    ');}
    
    const int maxn = 1e6+10;
    const int maxm = 2e7+10;
    
    char s[maxn];
    int rt[maxn],n,m;
    
    // ========  Segment_Tree
    
    #define mid ((l+r)>>1)
    
    int SGT,ls[maxm],rs[maxm];
    
    void modify(int &p,int l,int r,int x) {
    	if(!p) p=++SGT;
    	if(l==r) return ;
    	if(x<=mid) modify(ls[p],l,mid,x);
    	else modify(rs[p],mid+1,r,x);
    }
    
    int query(int p,int l,int r,int x,int y) {
    	if(!p||x>y) return 0;
    	if(x<=l&&r<=y) return 1;
    	if(x<=mid&&query(ls[p],l,mid,x,y)) return 1;
    	if(y>mid&&query(rs[p],mid+1,r,x,y)) return 1;
    	return 0;
    }
    
    int merge(int x,int y) {
    	if(!x||!y) return x+y;
    	int p=++SGT;
    	ls[p]=merge(ls[x],ls[y]);
    	rs[p]=merge(rs[x],rs[y]);
    	return p;
    }
    
    // ========
    
    namespace sam {  // string S 's Suffix Automaton 
    	int cnt=1,qs=1,lstp=1;
    	int tr[maxn][27],par[maxn],ml[maxn],vis[maxn],t[maxn],r[maxn];
    	
    	void append(int x) {
    		int p=lstp,np=++cnt;ml[np]=ml[p]+1,vis[np]=1;lstp=np;
    		for(;p&&tr[p][x]==0;p=par[p]) tr[p][x]=np;
    		if(!p) return par[np]=qs,void();
    		int q=tr[p][x];
    		if(ml[p]+1<ml[q]) {
    			int nq=++cnt;ml[nq]=ml[p]+1;
    			memcpy(tr[nq],tr[q],sizeof tr[nq]);
    			par[nq]=par[q],par[q]=par[np]=nq;
    			for(;p&&tr[p][x]==q;p=par[p]) tr[p][x]=nq;
    		} else par[np]=q;
    	}
    
    	void prepare() {
    		scanf("%s",s+1);n=strlen(s+1);
    		for(int i=1;i<=n;i++) append(s[i]-'a'+1);
    		
    		for(int i=1;i<=cnt;i++) t[ml[i]]++;
    		for(int i=1;i<=n;i++) t[i]+=t[i-1];
    		for(int i=1;i<=cnt;i++) r[t[ml[i]]--]=i;  //toposort
    		
    		for(int i=cnt;i;i--) {
    			if(vis[r[i]]) modify(rt[r[i]],1,n,ml[r[i]]);
    			if(par[r[i]]) rt[par[r[i]]]=merge(rt[par[r[i]]],rt[r[i]]);
    		}
    	}
    }
    
    namespace sam2 {  //string T 's Suffix Automaton
    	int cnt,qs=1,lstp;
    	int tr[maxn][27],ml[maxn],pos[maxn],par[maxn],lim[maxn];
    
    	void init() {cnt=lstp=1;memset(tr[1],0,sizeof tr[1]);}
    
    	int newnode(int x) {ml[++cnt]=x;memset(tr[cnt],0,sizeof tr[cnt]),par[cnt]=0;return cnt;}
    
    	void append(int x) {
    		int p=lstp,np=newnode(ml[p]+1);pos[np]=ml[np];lstp=np;
    		for(;p&&tr[p][x]==0;p=par[p]) tr[p][x]=np;
    		if(!p) return par[np]=qs,void();
    		int q=tr[p][x];
    		if(ml[p]+1<ml[q]) {
    			int nq=newnode(ml[p]+1);pos[nq]=pos[q];
    			memcpy(tr[nq],tr[q],sizeof tr[nq]);
    			par[nq]=par[q],par[q]=par[np]=nq;
    			for(;p&&tr[p][x]==q;p=par[p]) tr[p][x]=nq;
    		} else par[np]=q;
    	}
    
    	ll solve() {
    		init();int L,R;
    		scanf("%s",s+1);m=strlen(s+1);read(L),read(R);
    
    		int now=qs,len=0;
    		for(int i=1;i<=m;i++) {
    			int x=s[i]-'a'+1;
    			append(x);
    			while(1) {       // run on automaton 1
    				if(sam :: tr[now][x]&&query(rt[sam ::tr[now][x]],1,n,L+len,R)) {
    					now=sam :: tr[now][x],len++;break;
    				}
    				if(!len) break;len--;
    				if(len==sam :: ml[sam :: par[now]]) now=sam :: par[now];
    			}lim[i]=len;
    		}
    
    		ll ans=0;
    		for(int i=2;i<=cnt;i++) ans+=max(0,ml[i]-max(lim[pos[i]],ml[par[i]]));
    		return ans;
    	}
    }
    
    int main() {
    	sam :: prepare();
    	int T;read(T);while(T--) write(sam2 :: solve());
    	return 0;
    }
    
  • 相关阅读:
    C# 如何保证对象线程内唯一:数据槽(CallContext)【转载】
    关于面试!(简历篇)
    以Windows服务方式运行ASP.NET Core程序【转载】
    .Net Core 项目在Windows服务中托管【转载】
    【转载】Ocelot网关的路由热更新
    Kafka的配置文件详细描述
    C#枚举中的位运算权限分配浅谈
    CSS 三角形
    Entity Framework 7 动态 DbContext 模型缓存 ModelCaching
    ASP.NET5 MVC6 利用Middleware 创建可访问HttpContext 的业务类工厂。(代替HttpContext.Current)
  • 原文地址:https://www.cnblogs.com/hbyer/p/10443836.html
Copyright © 2011-2022 走看看