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

    Description

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

    Input

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

    Output

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

    Sample Input

    4
    aabbbb
    cccccc
    aabaabaabaa
    bbaabaababaaba
    

    Sample Output

    3
    5
    4
    7
    

    Solution

    对于(95pts),直接(O(n^2))暴力就好了 话说noi的题部分分这么足的吗...

    想拿到最后五分就很恶心了....

    我们枚举(AA)这样的串,设(s[i])表示以(i)开头有多少个这样的串,(t[i])表示结束,那么答案就是:

    [ans=sum_{i=2}^{n}s[i]t[i-1] ]

    然后考虑怎么快速的求出这玩意。

    考虑枚举长度(L),然后枚举一个(i=k*L),令(j=i+L)

    (x=min(L-1,lcs(s[1..i-1],s[1..j-1])),y=min(L,lcp(s[i..n],s[j..n]))),这个可以通过构建后缀数组(rmq) (O(1))求出。

    那么如果(x+ygeqslant L),则代表我们找到了起始点在([i-x,i-x+(x+y-L+1)-1]),这里是因为我们一共找到了(x+y-L+1)个合法串,所以拿一个差分累和就好了。

    时间复杂度:(O(nlog n+sum_{i=1}^{n}n/i)=O(nlog n))

    #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 = 4e5+10;
    
    char s[maxn];
    int n,cnt,sp1[maxn],sp2[maxn],sum[maxn],sa[maxn],rk[maxn],h[maxn],lg[maxn],r[maxn][16];
    int w1[maxn],rev[maxn],w2[maxn];
    
    void clear() {
    	memset(s,0,(cnt+10)*4);
    	memset(w1,0,(cnt+10)*4);
    	memset(w2,0,(cnt+10)*4);
    	memset(sp1,0,(cnt+10)*4);    // Attention !!
    	memset(sp2,0,(cnt+10)*4);
    }
    
    void get_sa() {
    	int *x=sp1,*y=sp2,m=200;
    	for(int i=1;i<=m;i++) sum[i]=0;
    	for(int i=1;i<=cnt;i++) sum[x[i]=s[i]]++;
    	for(int i=1;i<=m;i++) sum[i]+=sum[i-1];
    	for(int i=1;i<=cnt;i++) sa[sum[x[i]]--]=i;
    	
    	for(int k=1,p=0,t=0;t!=cnt;k<<=1,p=0) {
    		for(int i=cnt-k+1;i<=cnt;i++) y[++p]=i;
    		for(int i=1;i<=cnt;i++) if(sa[i]>k) y[++p]=sa[i]-k;
    
    		for(int i=1;i<=m;i++) sum[i]=0;
    		for(int i=1;i<=cnt;i++) sum[x[y[i]]]++;
    		for(int i=1;i<=m;i++) sum[i]+=sum[i-1];
    		for(int i=cnt;i;i--) sa[sum[x[y[i]]]--]=y[i];
    
    		swap(x,y);x[sa[t=1]]=1;
    		for(int i=2;i<=cnt;i++)
    			if(y[sa[i]]!=y[sa[i-1]]||y[sa[i]+k]!=y[sa[i-1]+k]) x[sa[i]]=++t;
    			else x[sa[i]]=t;
    		m=t;
    	}
    }
    
    void get_height() {
    	for(int i=1;i<=cnt;i++) rk[sa[i]]=i;
    	for(int i=1,p=0;i<=cnt;i++) {
    		if(p) p--;
    		while(s[i+p]==s[sa[rk[i]-1]+p]) p++;
    		h[rk[i]]=p;
    	}
    }
    
    void get_rmq() {
    	for(int i=2;i<=cnt;i++) lg[i]=lg[i>>1]+1;
    	for(int i=2;i<=cnt;i++) r[i][0]=h[i];
    	for(int i=1;i<=15;i++)
    		for(int j=2;j<=cnt-(1<<(i-1));j++)
    			r[j][i]=min(r[j][i-1],r[j+(1<<(i-1))][i-1]);
    }
    
    int rmq(int x,int y) {
    	if(x>y) return 0;
    	int t=lg[y-x];
    	return min(r[x][t],r[y-(1<<t)+1][t]);
    }
    
    int lcp(int x,int y) {
    	if(rk[x]>rk[y]) swap(x,y);
    	return rmq(rk[x]+1,rk[y]);
    }
    
    void add1(int x,int y) {w1[x]++,w1[y+1]--;}
    void add2(int x,int y) {w2[x]++,w2[y+1]--;}
    
    void solve() {
    	scanf("%s",s+1);n=cnt=strlen(s+1);s[++cnt]='#';
    	for(int i=1;i<=n;i++) s[++cnt]=s[n-i+1],rev[n-i+1]=cnt;
    	get_sa(),get_height(),get_rmq();
    	for(int L=1;L<=n;L++) {
    		for(int i=L;i<=n;i+=L) {
    			int j=i+L;if(j>n) break;
    			int x=min(L-1,lcp(rev[i-1],rev[j-1])),y=min(L,lcp(i,j));
    			if(x+y<L) continue;
    			add1(i-x,i-x+(x+y-L+1)-1);
    			add2(j+y-(x+y-L+1),j+y-1);
    		}
    	}
    	for(int i=1;i<=n;i++) w1[i]+=w1[i-1],w2[i]+=w2[i-1];
    	ll ans=0;
    	for(int i=1;i<=n;i++) ans+=1ll*w1[i]*w2[i-1];
    	write(ans);
    }
    
    int main() {
    	int T;read(T);while(T--) solve(),clear();
    	return 0;
    }
    
  • 相关阅读:
    asp.net c#中去掉最后一个字符和去掉第一个字母
    两个div并排
    VS.Net2005中使用本地化功能实现多语言的切换
    gridview嵌套DropDownList選定值[转]
    C# 获取系统时间
    NERDTree,好用的文件浏览器
    通过$.browser来判断浏览器
    vim 智能提示
    让vim显示函数列表
    vim中文乱码解决方法
  • 原文地址:https://www.cnblogs.com/hbyer/p/10451523.html
Copyright © 2011-2022 走看看