[HDU5659]CA Loves Substring/[JZOJ4705]knight

题目大意

给定一个长度为 n 的字符串(只包含数字),我们定义Fi表示在第 i 个字符后面断开后,两个字符串的本质不同的子串个数。请快速求出F
1n5×105


题目分析

这道题正着很难做(xdl好像在考场上做到了 O(nlog2n) )。
我们考虑正难则反:本来我们是对每个 i 求有多少个本质不同的子串至少有一次出现不包括位置i,现在我们转化为求每次出现都包括 i ,然后使用总的本质不同子串个数减去它。
先考虑一个子串S,令它最小出现位置为 minp ,最大出现位置为 maxp ,长度为 len ,那么我们分情况讨论:

  • minpmaxplen ,如果这样的话,这个串至少有两个出现位置是不重叠的,那么它对答案是没有任何贡献的。
  • minp>maxplen ,这时候这个子串就会区间 [maxplen,minp) F 做出1的贡献

但是子串的个数是 O(n2) 的,我们总不能依次枚举吧。像这种统计子串信息的题目,我们应该想到后缀自动机。但是后缀自动机里面,一个节点可能代表多个子串,那么情况就会变得复杂一点。
令节点 x 的最小出现位置为minp,最大出现位置为 maxp ,最小长度为 minl ,最大长度为 maxl 。前两个值可以使用类似统计 size 的方法按照拓扑序统计出来,第三个值就是 parent(x) len 1 ,第四个值就是len(x)
首先如果 minpmaxpmaxl ,那么这个节点对答案是没有贡献的。
否则这个节点对 F 的贡献相当于区间加上一个首项1共差 1 的等差数列,然后再区间加同一个值。对于区间(maxpmaxl,maxpminl+1] F ,贡献就分别是1,2,3,4...(每次增加一个能够覆盖的子串)。对于区间 (maxpminl+1,minp) F ,贡献则是rl+1(该节点所有子串都能覆盖这里)。
这里我们要支持加入一个等差数列,还有区间加一个值。这个不带 log 怎么做啊?
然而这里我们不一定要在线。考虑对 F 数组差分得到fi=FiFi1。那么区间加工差为 1 的等差数列,相当于给一个区间加上1,这个是可以使用前缀和统计的。后面那部分加同样的数的区间,我们之间单点修改它的结束位置与上一位的差( rl+1 )。
然后最后再次做一遍前缀和就是答案了。
时间复杂度 O(n) ,如果还有疑惑可以参看一下代码实现。
好久没有打后缀自动机,都快要忘了,一个下午复习了一下,总算记起来了。


代码实现

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cctype>
#include <queue>

using namespace std;

const int MOD=1000000007;
const int P=100013;
const int N=500050;
const int S=N<<1;
const int C=10;

int add[N],f[N],cnt[N],a[S];
int n,suf,tot,sum,ans;
char s[N];

struct node
{
    int mxp,mip,mxl,mil,len,prt;
    int next[C];
}sam[S];

int insert(int last,int c,int pos)
{
    int np=++tot,p=last,q;
    for (sam[np].mip=sam[np].mxp=pos,sam[np].len=sam[p].len+1;p&&!sam[p].next[c];p=sam[p].prt) sam[p].next[c]=np;
    if (!p) sam[np].prt=1;
    else
        if (sam[q=sam[p].next[c]].len==sam[p].len+1) sam[np].prt=q;
        else
        {
            int nq=++tot;
            sam[nq]=sam[q],sam[nq].len=sam[p].len+1;
            sam[nq].mip=n,sam[nq].mxp=-1;
            sam[q].prt=sam[np].prt=nq;
            for (;p&&sam[p].next[c]==q;p=sam[p].prt) sam[p].next[c]=nq;
        }
    return np;
}

void sorts()
{
    for (int i=1;i<=tot;i++) cnt[sam[i].len]++;
    for (int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
    for (int i=1;i<=tot;i++) a[cnt[sam[i].len]--]=i;
}

int main()
{
    freopen("knight.in","r",stdin),freopen("knight.out","w",stdout);
    scanf("%d",&n),scanf("%s",s),tot=suf=1;
    for (int i=0;i<n;i++) suf=insert(suf,s[i]-'0',i);
    for (int i=tot;i>1;i--) (sum+=(sam[i].len-sam[sam[i].prt].len))%=MOD;
    sorts();
    for (int i=tot,x;i>1;i--)
    {
        sam[x=sam[a[i]].prt].mip=min(sam[x].mip,sam[a[i]].mip),sam[x].mxp=max(sam[x].mxp,sam[a[i]].mxp);
        sam[a[i]].mxl=sam[a[i]].len,sam[a[i]].mil=sam[x].len+1;
    }
    for (int i=2;i<=tot;i++)
    {
        int l=sam[i].mil,r=sam[i].mxl;
        l=max(l,sam[i].mxp-sam[i].mip+1);
        if (l>r) continue;
        add[sam[i].mxp-r+1]++;
        add[sam[i].mxp-l+2]--;
        (((f[sam[i].mip]-=(r-l+1))%=MOD)+=MOD)%=MOD;
    }
    for (int k=0,i=0;i<n;i++) (k+=add[i])%=MOD,(f[i]+=k)%=MOD;
    for (int i=1;i<n;i++) (f[i]+=f[i-1])%=MOD;
    for (int i=0;i<n;i++) f[i]=(sum-f[i]+MOD)%MOD;
    for (int i=n-2,pw=1;i>=0;i--,pw=1ll*pw*P%MOD) (ans+=1ll*f[i]*pw%MOD)%=MOD;
    printf("%d\n",ans);
    fclose(stdin),fclose(stdout);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值