JZOJ4705.Knight

大意

给你一个长度为N(N<=500000)的字符串S,字符集为0~9,从1开始编号。设F[i]为S[1~i]与S[i+1~N]两个串所能构成的本质不同的串。求 N1j=1F[j]100013Nj1mod(109+7)

分析

暴力O( N3) :直接枚举i,然后枚举左右两边每个子串,hash判断即可。

暴力O(N^2):往深处想想。本质不同的串,我们可以想想后缀自动机或者后缀数组能不能用上。我比较熟悉SA,提出了许多猜想,都不行,主要原因是从某个位置断开后,Height数组变化很大。
那就想想SAM吧,直接求不同的串会比较麻烦,考虑反过来:首先求出原串有多少个本质不同的串ans,然后对于每个位置i,我们只用统计有多少种本质不同的子串,每种的所有串都恰好经过了断开的地方。我们记为G[i],那么F[i]=ans-G[i]。

考虑一种串 Si 对G数组的贡献,我们记此串的长度为len,right集合最大位置为Max,最小为Min,那么:
1,当Max-len>=Min,总会有两个串不相交,那么无论i在哪里,这种串都会计入F[i],所以对G[i]无贡献;
2,当Max-len

代码

#include<cstdio>
#include<algorithm>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
typedef long long ll;
const int N=500005,mo=1000000007;
char ch;
ll f[N],g[N],prt,ans,tim[N];
int e[N*2][10],parent[N*2],mx[N*2],Max[N*2],Min[N*2],last[N],np,p,q,nq;
int b[N*4],next[N*4],first[N*2];
int i,j,n,l,r,x,ts,tt,mn;
void cr(int x,int y)
{
    tt++;
    b[tt]=y;
    next[tt]=first[x];
    first[x]=tt;
}
int insert(int last,int x)
{
    np=++ts;
    mx[np]=mx[last]+1;
    Min[np]=Max[np]=i;
    int w;
    for(p=last;p>=0&&e[p][x]==0;p=parent[p]) 
        e[p][x]=np;
    if (p<0) parent[np]=0; else
    {
        q=e[p][x];
        if (mx[q]==mx[p]+1) parent[np]=q;else
        {
            nq=++ts;
            mx[nq]=mx[p]+1;
            parent[nq]=parent[q];
            Min[nq]=Min[q];
            Max[nq]=Max[q];
            parent[q]=parent[np]=nq;
            fo(w,0,9) e[nq][w]=e[q][w];
            for(;p>=0&&e[p][x]==q;p=parent[p]) e[p][x]=nq;
        }
    }
    ans+=mx[np]-mx[parent[np]];
    return np;
}
void dfs(int x,int y)
{
    for(int p=first[x];p;p=next[p])
        if (b[p]!=y)
        {
            dfs(b[p],x);
            Min[x]=min(Min[x],Min[b[p]]);
            Max[x]=max(Max[x],Max[b[p]]);
        }
}
int main()
{
    scanf("%d\n",&n);
    parent[0]=-1;
    fo(i,1,n)
    {
        scanf("%c",&ch);
        last[i]=insert(last[i-1],ch-'0');
    }
    fo(i,1,ts) 
    {
        cr(i,parent[i]);
        cr(parent[i],i);
    }
    dfs(0,0);
    tim[0]=1;
    fo(i,1,n) tim[i]=tim[i-1]*100013%mo;
    fo(i,1,ts)
    {
        x=i;
        mn=mx[parent[x]]+1;
        if (Max[x]-mx[x]>=Min[x]) continue;
        l=Max[x]-mx[x]+1;
        r=min(Max[x]-mn+1,Min[x]-1);
        g[l]++;
        g[r+1]--;
        r++;
        f[Min[x]]-=r-l;
    }
    fo(i,1,n) g[i]+=g[i-1];
    fo(i,1,n) f[i]+=f[i-1]+g[i];
    fo(i,1,n-1) 
    {
        prt=(prt+(ans-f[i])%mo*tim[n-i-1]%mo)%mo;
    }
    printf("%lld",prt);
}

反思

1,SAM的部分中,循环的变量总是与外界冲突,导致错误,下次要尽量把所有变量都变为局部,虽然会一定程度上影响调试;
2,对SAM的性质掌握不够透彻:要计算所有的本质不同的串对答案的贡献,就应该每个状态都枚举一遍,我天真地只搞last[1~n]的状态,漏掉了不少串;
3,考虑SAM构造方法,某个点X的parent只有可能是编号小的点,所以我们不需要真的构建parent树然后跑dfs,只需要编号从大到小扫一遍就行了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值