zoukankan      html  css  js  c++  java
  • BZOJ4650/UOJ219 [Noi2016]优秀的拆分

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作。

    本文作者:ljh2000
    作者博客:http://www.cnblogs.com/ljh2000-jump/
    转载请注明出处,侵权必究,保留最终解释权!

     

    Description

    如果一个字符串可以被拆分为 AABBAABB 的形式,其中 AA 和 BB 是任意非空字符串,则我们称该字符串的这种拆
    分是优秀的。例如,对于字符串 aabaabaa,如果令 A=aabA=aab,B=aB=a,我们就找到了这个字符串拆分成 AABBA
    ABB 的一种方式。一个字符串可能没有优秀的拆分,也可能存在不止一种优秀的拆分。比如我们令 A=aA=a,B=baa
    B=baa,也可以用 AABBAABB 表示出上述字符串;但是,字符串 abaabaa 就没有优秀的拆分。现在给出一个长度为
     nn 的字符串 SS,我们需要求出,在它所有子串的所有拆分方式中,优秀拆分的总个数。这里的子串是指字符串
    中连续的一段。
     
    以下事项需要注意:
     
    出现在不同位置的相同子串,我们认为是不同的子串,它们的优秀拆分均会被记入答案。在一个拆分中,允许出现
     A=BA=B。例如 cccc 存在拆分 A=B=cA=B=c。字符串本身也是它的一个子串。
     

    Input

    每个输入文件包含多组数据。输入文件的第一行只有一个整数 TT,表示数据的组数。保证 1≤T≤10。接
    下来 TT 行,每行包含一个仅由英文小写字母构成的字符串 SS,意义如题所述。N<=30000

     

     

    Output

    输出 TT 行,每行包含一个整数,表示字符串 SS 所有子串的所有拆分中,总共有多少个是优秀的拆分。
     

    Sample Input

    4
    aabbbb
    cccccc
    aabaabaabaa
    bbaabaababaaba

    Sample Output

    3
    5
    4
    7
    explanation
    我们用 S[i,j]S[i,j] 表示字符串 SS 第 ii 个字符到第 jj 个字符的子串(从 11 开始计数)。
    第一组数据中,共有 33 个子串存在优秀的拆分:
    S[1,4]=aabbS[1,4]=aabb,优秀的拆分为 A=aA=a,B=bB=b;
    S[3,6]=bbbbS[3,6]=bbbb,优秀的拆分为 A=bA=b,B=bB=b;
    S[1,6]=aabbbbS[1,6]=aabbbb,优秀的拆分为 A=aA=a,B=bbB=bb。
    而剩下的子串不存在优秀的拆分,所以第一组数据的答案是 33。
    第二组数据中,有两类,总共 44 个子串存在优秀的拆分:
    对于子串 S[1,4]=S[2,5]=S[3,6]=ccccS[1,4]=S[2,5]=S[3,6]=cccc,它们优秀的拆分相同,均为 A=cA=c,B=cB=c,但由于这些子串位置不同,因此要计算 33 次;
    对于子串 S[1,6]=ccccccS[1,6]=cccccc,它优秀的拆分有 22 种:A=cA=c,B=ccB=cc 和 A=ccA=cc,B=cB=c,它们是相同子串的不同拆分,也都要计入答案。
    所以第二组数据的答案是 3+2=53+2=5。
    第三组数据中,S[1,8]S[1,8] 和 S[4,11]S[4,11] 各有 22 种优秀的拆分,其中 S[1,8]S[1,8] 是问题描述中的例子,所以答案是 2+2=42+2=4。
    第四组数据中,S[1,4]S[1,4],S[6,11]S[6,11],S[7,12]S[7,12],S[2,11]S[2,11],S[1,8]S[1,8] 各有 11 种优秀的拆分,S[3,14]S[3,14] 有 22 种优秀的拆分,所以答案是 5+2=75+2=7。
     
     
    正解:二分+哈希 or  后缀数组
    解题报告:
      考虑只需求出以每个点开头的$AA$串和每个点结尾的$AA$串,然后相邻位置组合即可得到答案。
      那么我们考虑每次枚举一个长度$L$,然后每隔$L$建一个关键点,容易发现,每个长度为$L*2$的$AA$串,一定会经过两个关键点,观察相邻两个关键点的关系, 发现我们需要求出相邻两个关键点往前拓展的$LCS$和往后拓展的$LCP$,组合之后可以得到一个$A$串,两个位置对应相等。
      根据位置关系,我们可以确定从一段区间的开始都可以得到一个$AA$串。
      这样做的复杂度为调和级数,也就是$O(nlogn)$,加上每次二分得到$LCP$、$LCS$的复杂度就是两个$log$。
      同时因为有多次修改,而只需查询一次,我们用差分维护即可。
     
     
    //It is made by ljh2000
    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <cmath>
    #include <algorithm>
    #include <ctime>
    #include <vector>
    #include <queue>
    #include <map>
    #include <set>
    #include <string>
    #include <complex>
    using namespace std;
    typedef long long LL;
    const int MAXN = 50011;
    const int MOD = 30000007;
    int n;
    char ch[MAXN];
    LL hash[MAXN],mo[MAXN],u[MAXN],v[MAXN],ans;
    //u[i]记录i开头的AA的方案数,v[i]记录i结尾的AA的方案数,均为差分数组
    
    inline int getint(){
        int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar();
        if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w;
    }
    
    inline LL gethash(int l,int r){
    	LL now=hash[l]-hash[r]*mo[r-l];
    	now%=MOD; now+=MOD; now%=MOD;
    	return now;
    }
    
    inline void work(){
    	int T=getint(); mo[0]=1; for(int i=1;i<=30000;i++) mo[i]=mo[i-1]*31,mo[i]%=MOD;
    	while(T--) {
    		scanf("%s",ch+1); n=strlen(ch+1); int l,r,mid,head,tail,last,pos;
    		memset(u,0,sizeof(u)); memset(v,0,sizeof(v));
    		hash[n+1]=0; for(int i=n;i>=1;i--) hash[i]=hash[i+1]*31+ch[i]-'a'+1,hash[i]%=MOD;
    		for(int L=1;L*2<=n;L++)//枚举一个A的长度,则AA长度为2*L
    			for(int i=L+L;i<=n;i+=L) { //和后面的块求lcp,和前面的块求lcs
    				if(ch[i]!=ch[i-L]) continue;
    				//lcs
    				l=1; r=L; last=i-L; pos=0;
    				while(l<=r) {
    					mid=(l+r)>>1;
    					if(gethash(last-mid+1,last+1)==gethash(i-mid+1,i+1)) l=mid+1,pos=mid;
    					else r=mid-1;
    				}
    				head=i-pos+1;
    
    				//lcp
    				l=1; r=L; pos=0;
    				while(l<=r){
    					mid=(l+r)>>1; 
    					if(gethash(last,last+mid)==gethash(i,i+mid)) l=mid+1,pos=mid;
    					else r=mid-1;
    				}
    				tail=i+pos-1;
    
    				head=max(head+L-1,i);
    				tail=min(tail,i+L-1);
    
    				if(head<=tail) {
    					u[head-L*2+1]++; u[tail+1-L*2+1]--;
    					v[head]++; v[tail+1]--;
    				}
    			}
    		
    		ans=0;
    		for(int i=1;i<=n;i++) u[i]+=u[i-1],v[i]+=v[i-1];
    		for(int i=1;i<n;i++) ans+=v[i]*u[i+1];
    		printf("%lld
    ",ans);
    	}
    }
    
    int main()
    {
        work();
        return 0;
    }
    

      

  • 相关阅读:
    JVM内存模型与类加载机制
    JS 实现动态轮播图
    Jedis & spring-data-redis
    JAVA反射机制与动态代理
    JavaScript -- 筑基
    IO流与装饰者模式
    ES&IK环境搭建
    Elasticsearch笔记
    DQL
    DDL--DML
  • 原文地址:https://www.cnblogs.com/ljh2000-jump/p/6370364.html
Copyright © 2011-2022 走看看