• 【NOI2016】优秀的拆分


    题目描述

    如果一个字符串可以被拆分为 $AABB$ 的形式,其中 $A$ 和 $B$ 是任意非空字符串,则我们称该字符串的这种拆分是优秀的。

    例如,对于字符串 aabaabaa,如果令 $A = mathrm{aab}$,$B = mathrm{a}$,我们就找到了这个字符串拆分成 $AABB$ 的一种方式。

    一个字符串可能没有优秀的拆分,也可能存在不止一种优秀的拆分。比如我们令 $A=mathrm{a}$,$B=mathrm{baa}$,也可以用 $AABB$ 表示出上述字符串;但是,字符串 abaabaa 就没有优秀的拆分。

    现在给出一个长度为 $n$ 的字符串 $S$,我们需要求出,在它所有子串的所有拆分方式中,优秀拆分的总个数。这里的子串是指字符串中连续的一段。

    以下事项需要注意:

    1. 出现在不同位置的相同子串,我们认为是不同的子串,它们的优秀拆分均会被记入答案。
    2. 在一个拆分中,允许出现 $A=B$。例如 cccc 存在拆分 $A=B=mathtt{c}$。
    3. 字符串本身也是它的一个子串。

    输入格式

    每个输入文件包含多组数据。输入文件的第一行只有一个整数 $T$,表示数据的组数。保证 $1 le T le 10$。

    接下来 $T$ 行,每行包含一个仅由英文小写字母构成的字符串 $S$,意义如题所述。

    输出格式

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

    限制与约定

    对于全部的测试点,保证 $1 le T le 10$。以下对数据的限制均是对于单组输入数据而言的,也就是说同一个测试点下的 $T$ 组数据均满足限制条件。

    我们假定 $n$ 为字符串 $S$ 的长度,每个测试点的详细数据范围见下表:

    测试点编号 $n$ 其他约束
    1、2$leq 300$$S$中所有字符全部相同
    3、4$leq 2000$
    5、6$leq 10$
    7、8$leq 20$
    9、10$leq 30$
    11、12$leq 50$
    13、14$leq 100$
    15$leq 200$
    16$leq 300$
    17$leq 500$
    18$leq 1000$
    19$leq 2000$
    20$leq 30000$

    暴力分析

    似乎暴力就有95分啊?

    (O(n^2))预处理双hash,用来判断子串是否相同

    然后(O(n^2))处理(f[i])(f[i])表示结尾位置为(i),满足(AA)的子串数量

    然后可以直接根据(f[i])(O(n^2))的的时间内得到(g[i]),(g[i])表示,结尾为i满足(AABB)的子串数量

    很基础啊?

    #include<cstdio>  
    #include<iostream>  
    #include<algorithm>  
    #include<cstdlib>  
    #include<cstring>
    #include<string>
    #include<climits>
    #include<vector>
    #include<cmath>
    #include<map>
    #define LL long long
     
    using namespace std;
     
    inline char nc(){
      static char buf[100000],*p1=buf,*p2=buf;
      if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
      return *p1++;
    }
     
    inline void read(int &x){
      char c=nc();int b=1;
      for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
      for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
    }
     
    inline void read(LL &x){
      char c=nc();LL b=1;
      for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
      for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
    }
    
    inline int read(char *s)
    {
    	char c=nc();int len=1;
    	for(;!(c>='a' && c<='z');c=nc()) if (c==EOF) return 0;
    	for(;(c>='a' && c<='z');s[len++]=c,c=nc());
    	s[len++]='';
    	return len;
    }
    
    inline void read(char &x){
      for (x=nc();!(x>='A' && x<='Z');x=nc());
    }
    
    int wt,ss[19];
    inline void print(int x){
    	if (x<0) x=-x,putchar('-'); 
    	if (!x) putchar(48); else {
    	for (wt=0;x;ss[++wt]=x%10,x/=10);
    	for (;wt;putchar(ss[wt]+48),wt--);}
    }
    inline void print(LL x){
    	if (x<0) x=-x,putchar('-');
    	if (!x) putchar(48); else {for (wt=0;x;ss[++wt]=x%10,x/=10);for (;wt;putchar(ss[wt]+48),wt--);}
    }
    
    int T,n,f1[2010][2010],f2[2010][2010],f[2010],g[2010];
    char s[2010];
    const int mo1=100271,mo2=500179;
    
    void init()
    {
    	memset(f1,0,sizeof(f1));
    	memset(f2,0,sizeof(f2));
    	for (int i=1;i<=n;i++)
    	{
    		for (int j=i;j<=n;j++)
    			f1[i][j]=(f1[i][j-1]*28%mo1+s[j]-'a'+1)%mo1,
    			f2[i][j]=(f2[i][j-1]*28%mo2+s[j]-'a'+1)%mo2;
    	}
    }
    
    int main()
    {
    	read(T);
    	while (T--)
    	{
    		read(s);
    		n=strlen(s+1);
    		init();
    		memset(f,0,sizeof(f));
    		memset(g,0,sizeof(g));
    		for (int i=1;i<=n;i++)
    		{
    			int j,x;
    			if (i%2==1) j=2;else j=1;
    			for (;j<=i-1;j+=2)
    			{
    				x=i-j+1;x=j+x/2-1;
    				if (f1[j][x]==f1[x+1][i] && f2[j][x]==f2[x+1][i]) f[i]++;
    			}
    		}
    		for (int i=3;i<=n;i++)
    		{
    			int s=f[i-1],j=i-2,x;
    			for (int j=i+1;j<=n;j+=2)
    			{
    				x=j-i+1;x=i+x/2-1;
    				if (f1[i][x]==f1[x+1][j] && f2[i][x]==f2[x+1][j]) g[j]+=s;
    			}
    		}
    		int ans=0;
    		for (int i=4;i<=n;i++)
    			ans+=g[i];
    		print(ans),puts("");
    	}
    	return 0;
    }
    

    满分算法分析

    当时考场上没打算为了这5分再去思考啊

    不过正解的思想还是很不错的

    基于上面的思想,我们可以看到$$ans=sum_{i=1}^{i<n} f[i]*g[i+1]$$其中(f[i])表示以第(i)位作为结束位的形如(AA)的个数,(g[i])表示以第(i)位作为开始位的形如(AA)的个数

    现在的问题就是怎么快速的求(f[i])(g[i])

    在UOJ群上围观了Claris秒题以后,大概知道了怎么弄QAQ

    我们枚举(AA)串中(A)的长度(L)。在原串上,我们每隔(L)设置一个关键点,可以发现,若(A)的长度为(L)(AA)必定恰好经过某两个相邻的关键点。于是我们可以枚举(AA)经过的关键点

    考虑两个相邻的关键点(a,b),有(b=a+L),我们求出(a,b)的最长公共前缀(p)和最长公共后缀(s)。若(p+s>L)(AA)串就一定存在,可以画个图来直观理解一下

    那么,我们就可以直接得出可行的开始位置的区间为([a-s+1,a+p-l]),可行的结束位置为([b-s+l,b+p-1])

    直接暴力枚举的时间复杂度是(T(n)=sum_{i=1}^{n} frac{n}{i}=nlogn)


    现在还有一个问题是怎么求(a,b)的最长公共前缀(p)和最长公共后缀(s)

    首先可以看一下uoj35,他所求的是相邻(rank)的LCP

    我们假设(h[i]=LCP{suffix(sa[i-1]),suffix(sa[i])})

    可以得到对于任意的(j)(k)(假设(rank[j]<rank[k])),(LCP{suffix(j),suffix(k)}=min{h[rank[j]+1],h[rank[j]+2],cdots ,height[rank[k]]})

    直接用ST预处理,每次询问都是(O(1)),对上述复杂度无影响

    PS.注意,我的代码使用SAM来构造SA的,由于SAM建立的时候会有新的节点产生,所以数组需要开大一些,不然会gg


    然后最后的一份问题就是要进行区间加1的操作,直接差分即可,不要再往复杂度上加无谓的log

    #include<cstdio>  
    #include<iostream>  
    #include<algorithm>  
    #include<cstdlib>  
    #include<cstring>
    #include<string>
    #include<climits>
    #include<vector>
    #include<cmath>
    #include<map>
    #define LL long long
     
    using namespace std;
     
    inline char nc(){
      static char buf[100000],*p1=buf,*p2=buf;
      if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
      return *p1++;
    }
     
    inline void read(int &x){
      char c=nc();int b=1;
      for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
      for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
    }
     
    inline void read(LL &x){
      char c=nc();LL b=1;
      for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
      for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
    }
    
    inline int read(char *s)
    {
    	char c=nc();int len=0;
    	for(;!(c>='a' && c<='z');c=nc()) if (c==EOF) return 0;
    	for(;(c>='a' && c<='z');s[len++]=c,c=nc());
    	s[len++]='';
    	return len;
    }
    
    inline void read(char &x){
      for (x=nc();!(x>='A' && x<='Z');x=nc());
    }
    
    int wt,ss[19];
    inline void print(int x){
    	if (x<0) x=-x,putchar('-'); 
    	if (!x) putchar(48); else {
    	for (wt=0;x;ss[++wt]=x%10,x/=10);
    	for (;wt;putchar(ss[wt]+48),wt--);}
    }
    inline void print(LL x){
    	if (x<0) x=-x,putchar('-');
    	if (!x) putchar(48); else {for (wt=0;x;ss[++wt]=x%10,x/=10);for (;wt;putchar(ss[wt]+48),wt--);}
    }
    
    int n,m,s,b[80010],c[80010],d[80010],f[80010],g[80010];
    char sx[80010];
    struct data
    {
        int len,fa,letter[26],tree[26],id,flag;
    }a[80010];
    int sa[80010],rank[80010],r1[80010],r2[80010],RANK,f1[80010][20],f2[80010][20];
    
    void Extend(int x,int p)
    {
        s++;int q=s;a[q].len=a[p].len+1;
        while (p!=0 && a[p].letter[x]==0)
            a[p].letter[x]=q,p=a[p].fa;
        if (p==0) {a[q].fa=1;return ;}
        int np=a[p].letter[x];
        if (a[np].len==a[p].len+1) a[q].fa=np;
        else
        {
            s++;int nq=s;a[nq].len=a[p].len+1;
            for (int i=0;i<26;i++)
                a[nq].letter[i]=a[np].letter[i];
            a[nq].id=a[np].id;
            a[nq].fa=a[np].fa;a[np].fa=nq;a[q].fa=nq;
            while (p!=0&&a[p].letter[x]==np)
                a[p].letter[x]=nq,p=a[p].fa;
        }
    }
    
    void Insert(char x[])
    {
        int y=strlen(x);
        s=1;int z=1;
        for (int i=y-1;i>=0;i--)
    	  Extend(x[i]-'a',z),z=a[z].letter[x[i]-'a'],a[z].id=i+1,d[i+1]=z,a[z].flag=1;
    }
    
    void dfs(int x)
    {
    	if (RANK>=30000)
    		print(1);
        if(a[x].id!=0 && a[x].flag) sa[++RANK]=a[x].id,rank[a[x].id]=RANK;
        for (int i=0;i<26;i++)
            if (a[x].tree[i]!=0) dfs(a[x].tree[i]);
    }
    
    void build()
    {
        for (int i=1;i<=s;i++)
            c[a[i].len]++;
        for (int i=1;i<=s;i++)
            c[i]+=c[i-1];
        for (int i=1;i<=s;i++)
            b[c[a[i].len]--]=i;
        for (int i=s;i>=1;i--)
        {
            int p=b[i];
            a[a[p].fa].tree[sx[a[p].id+a[a[p].fa].len-1]-'a']=p;
        }
        RANK=0;
        dfs(1);
    }
    
    LL Query(int x,int y)
    {
        if (x==y) return a[x].len;
        if (a[x].len>a[y].len) return Query(a[x].fa,y);
        else return Query(x,a[y].fa);
    }
    
    void SWAP(char *s)
    {
    	int x=strlen(s);
    	for (int i=0;i<x/2;i++)
    		swap(s[i],s[x-i-1]);
    }
    
    void init()
    {
    	memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        memset(c,0,sizeof(c));
        memset(d,0,sizeof(d));
        memset(sa,0,sizeof(sa));
        memset(rank,0,sizeof(rank));
    }
    
    int query1(int z,int y)
    {
    	z=r1[z],y=r1[y];
    	if (z>y) swap(z,y);z++;
        int x=(int)(log(y-z+1)/log(2));
        return min(f1[z][x],f1[y-(1<<x)+1][x]);
    }
    
    int query2(int z,int y)
    {
    	z=r2[z],y=r2[y];
    	if (z>y) swap(z,y);z++;
        int x=(int)(log(y-z+1)/log(2));
        return min(f2[z][x],f2[y-(1<<x)+1][x]);
    }
    
    void sa_init()
    {
    	n=strlen(sx);
        init();
        Insert(sx);
        build();
        for (int i=2;i<=n;i++)
        	f1[i][0]=Query(d[sa[i]],d[sa[i-1]]);
        f1[1][0]=0;
        for (int j=1;1<<j<=n;j++)
        	for (int i=1;i+(1<<j)-1<=n;i++)
        		f1[i][j]=min(f1[i][j-1],f1[i+(1<<j-1)][j-1]);
        for (int i=1;i<=n;i++)
        	r1[i]=rank[i];
        init();
        SWAP(sx);
        Insert(sx);
        build();
        for (int i=2;i<=n;i++)
        	f2[i][0]=Query(d[sa[i]],d[sa[i-1]]);
        f2[1][0]=0;
        for (int j=1;1<<j<=n;j++)
        	for (int i=1;i+(1<<j)-1<=n;i++)
        		f2[i][j]=min(f2[i][j-1],f2[i+(1<<j-1)][j-1]);
        for (int i=1;i<=n;i++)
        	r2[n-i+1]=rank[i];
    }
    
    int T;
    
    int main()
    {
    	read(T);
    	while (T--)
    	{
    		read(sx);
    		int m=strlen(sx),x,y,s,p;
    		sa_init();
    		memset(f,0,sizeof(f));
    		memset(g,0,sizeof(g));
    		for (int i=1;i<=m;i++)
    		{
    			x=1,y=x+i;
    			while(y<=m)
    			{ 
    				p=min(query1(x,y),i);
    				s=min(query2(x,y),i);
    				if (p+s>i)
    				{
    					f[x-s+1]++;f[x+p-i+1]--;
    					g[y-s+i]++;g[y+p]--;
    				}
    				x+=i,y+=i;
    			}
    		}
    		int F=0,G=0;
    		for (int i=1;i<=m;i++)
    			F+=f[i],f[i]=F,G+=g[i],g[i]=G;
    		LL ans=0;
    		for (int i=1;i<m;i++)
    			ans+=(LL)g[i]*f[i+1];
    		print(ans),puts("");
    	}
    	return 0;
    }
    
  • 相关阅读:
    进程和线程
    进程通信、同步与调度
    文件和文件系统
    【nexys3】【verilog】小设计——拆弹游戏
    Qt4开发环境搭建(Qt4.8.7+mingw4.8.2+Qt Creator4.2.0)
    GPL和LGPL
    【rpi】使用putty远程连接rpi(ssh)
    mysql 命令 小结
    安装mysql zip 安装包 Navicat连接
    python虚拟环境 virtualenv工具
  • 原文地址:https://www.cnblogs.com/xiejiadong/p/6720138.html
Copyright © 2020-2023  润新知