NOI 2016 优秀的拆分 (后缀数组)

5 篇文章 0 订阅
3 篇文章 0 订阅

题目大意:给你一个字符串,求所有子串的所有优秀拆分总和,优秀的拆分被定义为一个字符串可以被拆分成4个子串,形如AABB,其中AA相同,BB相同,AB也可以相同

作为一道国赛题,95分竟然就这么给我们了!只是一个NOIP难度的哈希套DP啊......

95分就是从后往前找,统计AA串,每次统计一下从这个位置开始的所有子串 和 紧随其后的等长串 相同的个数sum

hash(i,i+j-1)==hash(i+j,i+2*j-1) sum[i]++

然后再统计BB串,就是加上在这两个串之后的位置的sum值,这样就统计出了合法拆分数

dp[i]+=sum[i+2*j]

95分就这样到手啦

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long 
#define ull unsigned long long 
#define N 2050
#define seed 233
#define idx(x) (x-'a'+1)
using namespace std;
//re
int T,len;
char str[N];
int dp[N],sum[N];
ull hsh[N],sp[N];
ull ghsh(int x,int y) {return hsh[y]-hsh[x-1]*sp[y-x+1];}
void clr()
{
    memset(dp,0,sizeof(dp));
    memset(sp,0,sizeof(sp));
    memset(sum,0,sizeof(sum));
    memset(hsh,0,sizeof(hsh));
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        clr();
        scanf("%s",str+1);
        len=strlen(str+1);
        sp[0]=1;
        for(int i=1;i<=len;i++)
            hsh[i]=hsh[i-1]*seed+idx(str[i]),
            sp[i]=sp[i-1]*seed;
        for(int i=len-1;i>=1;i--)
        {
            for(int j=1;i+2*j-1<=len;j++)
            {
                if(ghsh(i,i+j-1)==ghsh(i+j,i+2*j-1)){
                    dp[i]++;
                    sum[i]+=dp[i+2*j];
                }
            }
        }
        int ans=0;
        for(int i=1;i<=len;i++)
            ans+=sum[i];
        printf("%d\n",ans);
    }
    return 0;
}





接下来才是本题的重点!NOI岂是你想AK就AK的!出题人貌似想针对NOI2015差5分AK的wzf巨佬

不看题解这个正解思路真是很难想到......

思路大概是这样的,其实我们每次只需要统计AA串的数量就行了,因为AABB串的数量等于,在i-1位结束的AA串的数量乘以在第i位开始的AA串的数量,用公式表示就是

\sum_{i=2}^{n} ed[i-1]*st[i]

接下来就是统计st和ed了,感觉正解的思路很神

先用后缀数组+ST表处理出任意两个位置,作为后缀串开头的LCP以及作为前缀串末尾的LCS,求LCS可以把串反着读再去套SA

每次选取一个A串的长度len,再在原串每隔len的位置设一个关键点

然后会发现一个神奇的性质,任意一个长度为2*len的串必然经过且仅经过2个关键点!

接下来统计经过所有长度为2*len所有AA串,比如选定两个关键点a,b,且a+len=b

如果把a的和b相同的前缀和后缀拼在一起,且拼完之后长度>=len,说明存在至少一个AA串

可以把AA串想象成一个块,在两个关键点上移动,块的左端点不能大于a,块的右端点不能小于b,块不能移动到上一个或者下一个关键点的位置,块内必须是AA串,这样,块所有能移动的位置-块的长度len,就是经过ab两个关键点的所有AA串数

时间变成O(nlogn),logn是调和级数的近似值

细节比较多需要仔细思考

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long 
#define N 30100
#define seed 233
#define idx(x) (x-'a'+1)
using namespace std;
//re
int T,len;
int lg[N];
ll st[N],ed[N],S[N],E[N];
struct suffix{
    char str[N];
    int sa[N],tr[N],rk[N],hs[N],h[N],f[N][15],len; 
    bool check(int k,int x,int y){
        if(x+k>len||y+k>len) return 0;
        else return (rk[x]==rk[y]&&rk[x+k]==rk[y+k])?1:0;
    }
    void get_suffix()
    {
        int cnt=0,i;len=strlen(str+1);
        for(i=1;i<=len;i++) hs[str[i]]++;
        for(i=1;i<=127;i++) if(hs[i]) tr[i]=++cnt;
        for(i=1;i<=127;i++) hs[i]+=hs[i-1];
        for(i=1;i<=len;i++) rk[i]=tr[str[i]],sa[hs[str[i]]--]=i;
        for(int k=1;cnt<len;k<<=1)
        {
            for(i=1;i<=cnt;i++) hs[i]=0;
            for(i=1;i<=len;i++) hs[rk[i]]++;
            for(i=1;i<=cnt;i++) hs[i]+=hs[i-1];
            for(i=len;i>=1;i--) if(sa[i]>k) tr[sa[i]-k]=hs[rk[sa[i]-k]]--;
            for(i=1;i<=k;i++) tr[len-i+1]=hs[rk[len-i+1]]--;
            for(i=1;i<=len;i++) sa[tr[i]]=i;
            for(i=1,cnt=0;i<=len;i++) tr[sa[i]]=check(k,sa[i],sa[i-1])?cnt:++cnt;
            for(i=1;i<=len;i++) rk[i]=tr[i];
        }
        for(i=1;i<=len;i++)
        {
            if(rk[i]==1) continue;
            for(int j=max(1,h[rk[i-1]]-1);;j++)
                if(str[i+j-1]==str[sa[rk[i]-1]+j-1]) h[rk[i]]=j;
                else break;
        }
    }
    void get_ST()
    {
        for(int i=1;i<=len;i++) 
            f[i][0]=h[i];
        for(int k=1;(1<<k)<=len;k++)
            for(int i=1;i+(1<<k)-1<=len;i++)
                f[i][k]=min(f[i][k-1],f[i+(1<<(k-1))][k-1]);
    }
    int query(int x,int y)
        {int L=y-x+1;
        return min(f[x][lg[L]],f[y-(1<<lg[L])+1][lg[L]]);}
}p,s;
void clr()
{
    memset(S,0,sizeof(S)),memset(st,0,sizeof(st));
    memset(E,0,sizeof(E)),memset(ed,0,sizeof(ed));
    memset(&p,0,sizeof(p)),memset(&s,0,sizeof(s));
}
int main()
{
    scanf("%d",&T);
    for(int i=2;i<=30010;i++)
        lg[i]=lg[i>>1]+1;
    while(T--)
    {
        clr();
        scanf("%s",s.str+1);
        int len=strlen(s.str+1);
        for(int i=1;i<=len;i++) 
            p.str[i]=s.str[len-i+1];
        s.get_suffix();
        s.get_ST();
        p.get_suffix();
        p.get_ST();
        int sx,sy,px,py,ss,sp,l,r;
        for(int i=1;i<=len;i++)
        {
            for(int j=i+1;j<=len;j+=i)
            {
                sx=s.rk[j-i+1],sy=s.rk[j+1];
                if(sx>sy) swap(sx,sy);
                px=p.rk[len-(j-i)+1],py=p.rk[len-j+1];
                if(px>py) swap(px,py);
                ss=s.query(sx+1,sy);
                sp=p.query(px+1,py);
                if(ss+sp<i||sp==0) continue;
                l=max(j-i-i+1,j-i-sp+1);
                r=min(j+i-1,j+ss);
                S[l]++,S[r-2*i+2]--;
                E[l+2*i-1]++,E[r+1]--;
            }
        }
        for(int i=1;i<=len;i++)
            st[i]=st[i-1]+S[i],ed[i]=ed[i-1]+E[i];
        ll ans=0;
        for(int i=2;i<=len;i++)
            ans+=ed[i-1]*st[i];
        printf("%lld\n",ans);
    }
    return 0;
}





 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值