【BZOJ4650】【NOI2016】优秀的拆分(后缀数组)

57 篇文章 0 订阅
14 篇文章 0 订阅

题面

BZOJ
Uoj

题解

如果我们知道以某个位置为开始/结尾的 AA 串的个数
那就直接做一下乘法就好
这个怎么求?
枚举一个位置
枚举串的长度
直接暴力算就好啦
至于是否可行,用 SA lcp 就好啦
这样就是 95
NOI这么好拿部分分的???

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 35000
int x[MAX],y[MAX],t[MAX];
int SA[MAX],height[MAX],rk[MAX];
int lg[MAX],n,p[20][MAX],a[MAX];
char s[MAX];
int g[MAX],f[MAX],T;
bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];}
void init()
{
    memset(SA,0,sizeof(SA));
    memset(height,0,sizeof(height));
    memset(rk,0,sizeof(rk));
    memset(x,0,sizeof(x));
    memset(y,0,sizeof(y));
    memset(t,0,sizeof(t));
    memset(a,0,sizeof(a));
}
void GetSA()
{
    int m=50;
    for(int i=1;i<=n;++i)t[x[i]=a[i]]++;
    for(int i=1;i<=m;++i)t[i]+=t[i-1];
    for(int i=n;i>=1;--i)SA[t[x[i]]--]=i;
    for(int k=1;k<=n;k<<=1)
    {
        int p=0;
        for(int i=n-k+1;i<=n;++i)y[++p]=i;
        for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k;
        for(int i=0;i<=m;++i)t[i]=0;
        for(int i=1;i<=n;++i)t[x[y[i]]]++;
        for(int i=1;i<=m;++i)t[i]+=t[i-1];
        for(int i=n;i>=1;--i)SA[t[x[y[i]]]--]=y[i];
        swap(x,y);
        x[SA[1]]=p=1;
        for(int i=2;i<=n;++i)
            x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p;
        if(p>=n)break;
        m=p;
    }
    for(int i=1;i<=n;++i)rk[SA[i]]=i;
    for(int i=1,j=0;i<=n;++i)
    {
        if(j)--j;
        while(a[i+j]==a[SA[rk[i]-1]+j])++j;
        height[rk[i]]=j;
    }
}
void Pre()
{
    memset(p,63,sizeof(p));
    for(int i=1;i<=n;++i)p[0][i]=height[i];
    for(int j=1;j<15;++j)
        for(int i=1;i<=n;++i)
            p[j][i]=min(p[j-1][i],p[j-1][i+(1<<(j-1))]);
}
int Query(int i,int j)
{
    return min(p[lg[j-i+1]][i],p[lg[j-i+1]][j-(1<<lg[j-i+1])+1]);
}
int lcp(int i,int j)
{
    int l=min(rk[i],rk[j])+1,r=max(rk[i],rk[j]);
    return Query(l,r);
}
int main()
{
    for(int i=2;i<=30000;++i)lg[i]=lg[i>>1]+1;
    scanf("%d",&T);
    while(T--)
    {
        init();
        scanf("%s",s+1);
        n=strlen(s+1);
        for(int i=1;i<=n;++i)a[i]=s[i]-96;
        GetSA();Pre();
        for(int i=1;i<=n;++i)
        {
            g[i]=0;
            for(int l=1;l+l+i-1<=n;++l)
                if(lcp(i,i+l)>=l)g[i]++;
        }
        for(int i=2;i<=n;++i)
        {
            f[i]=0;
            for(int l=1;i-l-l+1>0;++l)
                if(lcp(i-l-l+1,i-l+1)>=l)f[i]++;
        }
        int ans=0;
        for(int i=1;i<n;++i)
            ans+=f[i]*g[i+1];
        printf("%d\n",ans);
    }
    return 0;
}

95 分的暴力太显然了。。
原来 NOI 都是这样送分???
为什么NOIP 没有这么好的福利


想想怎么优化吧。。。
肯定不能枚举长度之后再暴力算每一个位置
那么,我们要考虑一个方法,
可以一次性算出连续的位置

想想我们怎么求 AA 这种形式??
计算 lcp(i,i+len)>=len 是否成立
但是,如果 lcp(i,i+len)>=len
我们就会发现,有一段区间内都是有满足条件的子串
所以我们可以一起计算

现在仔细思考怎么算
因为每次是 i i+len
所以我们只要枚举位置是 len 的倍数的地方就好
旁边的地方我们要想办法算出来
第一个,是向后如果可以增加的话
lcp(i,i+len)>=L 我就会获得向后的一段连续区间
如果只算向后,会忽略掉向前的一段
所以再算一下 lcs(i,i+len) 这段,这两边拼起来
如果满足条件,证明这一段区间都是可行的
这样就可以差分全部 +1
如果重复的部分够多
这样算可能会影响到别的块里面
所以要强制只在自己这一段里面算
具体的实现看代码啦

 #include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 35000
int lg[MAX],n;
char s[MAX];
int g[MAX],f[MAX],T;
struct SA
{
    int p[20][MAX],a[MAX];
    int x[MAX],y[MAX],t[MAX];
    int SA[MAX],height[MAX],rk[MAX];
    bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];}
    void init()
        {
            memset(SA,0,sizeof(SA));
            memset(height,0,sizeof(height));
            memset(rk,0,sizeof(rk));
            memset(x,0,sizeof(x));
            memset(y,0,sizeof(y));
            memset(t,0,sizeof(t));
            memset(a,0,sizeof(a));
        }
    void GetSA()
        {
            int m=50;
            for(int i=1;i<=n;++i)t[x[i]=a[i]]++;
            for(int i=1;i<=m;++i)t[i]+=t[i-1];
            for(int i=n;i>=1;--i)SA[t[x[i]]--]=i;
            for(int k=1;k<=n;k<<=1)
            {
                int p=0;
                for(int i=n-k+1;i<=n;++i)y[++p]=i;
                for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k;
                for(int i=0;i<=m;++i)t[i]=0;
                for(int i=1;i<=n;++i)t[x[y[i]]]++;
                for(int i=1;i<=m;++i)t[i]+=t[i-1];
                for(int i=n;i>=1;--i)SA[t[x[y[i]]]--]=y[i];
                swap(x,y);
                x[SA[1]]=p=1;
                for(int i=2;i<=n;++i)
                    x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p;
                if(p>=n)break;
                m=p;
            }
            for(int i=1;i<=n;++i)rk[SA[i]]=i;
            for(int i=1,j=0;i<=n;++i)
            {
                if(j)--j;
                while(a[i+j]==a[SA[rk[i]-1]+j])++j;
                height[rk[i]]=j;
            }
        }
    void Pre()
        {
            memset(p,63,sizeof(p));
            for(int i=1;i<=n;++i)p[0][i]=height[i];
            for(int j=1;j<15;++j)
                for(int i=1;i<=n;++i)
                    p[j][i]=min(p[j-1][i],p[j-1][i+(1<<(j-1))]);
        }
    int Query(int i,int j)
        {
            return min(p[lg[j-i+1]][i],p[lg[j-i+1]][j-(1<<lg[j-i+1])+1]);
        }
    int lcp(int i,int j)
        {
            int l=min(rk[i],rk[j])+1,r=max(rk[i],rk[j]);
            return Query(l,r);
        }
}A,B;
int main()
{
    for(int i=2;i<=30000;++i)lg[i]=lg[i>>1]+1;
    scanf("%d",&T);
    while(T--)
    {
        A.init();B.init();
        scanf("%s",s+1);
        n=strlen(s+1);
        for(int i=1;i<=n;++i)A.a[i]=s[i]-96;
        for(int i=1;i<=n;++i)B.a[n-i+1]=s[i]-96;
        A.GetSA();A.Pre();B.GetSA();B.Pre();
        for(int i=1;i<=n;++i)g[i]=f[i]=0;
        for(int len=1;len<=n/2;++len)
        {
            for(int i=len,j=i+len;j<=n;i+=len,j+=len)
            {
                int x=min(A.lcp(i,j),len);
                int y=min(B.lcp(n-i+2,n-j+2),len-1);
                int t=x+y-len+1;
                if(x+y>=len)
                {
                    g[i-y]++;g[i-y+t]--;
                    f[j+x-t]++;f[j+x]--;
                }
            }
        }

        for(int i=1;i<=n;++i)g[i]+=g[i-1];
        for(int i=1;i<=n;++i)f[i]+=f[i-1];
        ll ans=0;
        for(int i=1;i<n;++i)
            ans+=1ll*f[i]*g[i+1];
        printf("%lld\n",ans);
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值