[HDU5659]knight

题目大意

求一个字符串,从每一个位置分成两部分(当做两个字符串),求本质不同子串数量。

SAM

我们先有一个简单的思路。f[i]表示从i分成两部分的答案。
正难则反,求出原串本质不同子串数量,对于每个位置减去跨线且没在两边出现过的即可。
对于一个子串,如果其出现在原串中最大的右端点为mx,最小的右端点为mi,其长度为len。
如果mx-len>=mi,则因为其所有出现区间交为空,无论从哪个位置分开这个子串都会出现在其中一边。
如果 mxlen<mi ,那么f的[mx-len,mi-1]都可以加一,因为显然在区间交里都可以分离成功。
然而子串个数很多,怎么办呢?
考虑SAM,那么公用right集合的点被缩为一个点,枚举自动机上每个点,然后得到mx和mi(这里说一下mx和mi的求法,因为后缀自动机上一点的right集合是其parent树中子树的right集合的并,因此其mx等于其子树中mx的最大值,mi等于其子树中mi的最小值),同时要得到len的范围[l,r](后缀自动机上一个点的maxs就是step,mins就是其parent树父亲的maxs+1)
那么f的[mx-r+1,mx-l+1]是第一个+1,第二个+2,第三个+3……一直这样下去,然后f的[mx-l+2,mi-1]+r-l+1。
我们考虑线性做,可以设gi=fi-fi-1,那么区间加等差变成了区间+1,后面的平板只需要单点修改。区间加1可以通过开头挂+1结尾挂-1最后线性扫实现。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int maxn=500000+10,mo=1000000007;
int g[maxn*2][10],pre[maxn*2],step[maxn*2],mi[maxn*2],ma[maxn*2],mins[maxn*2],maxs[maxn*2],cnt[maxn*2],ad[maxn],f[maxn];
int a[maxn*2];
int i,j,k,l,r,t,n,m,tot,last,sum,ans;
char ch;
char get(){
    char ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    return ch;
}
void add(int x){
    int np=++tot,p=last;
    mi[tot]=100000000;
    step[np]=step[p]+1;
    while (p>=0&&g[p][x]==0){
        g[p][x]=np;
        p=pre[p];
    }
    if (p==-1) pre[np]=0;
    else{
        int q=g[p][x];
        if (step[q]==step[p]+1) pre[np]=q;
        else{
            int nq=++tot,i;
            mi[tot]=100000000;
            fo(i,0,9) g[nq][i]=g[q][i];
            pre[nq]=pre[q];
            pre[q]=nq;
            step[nq]=step[p]+1;
            pre[np]=nq;
            while (p>=0&&g[p][x]==q){
                g[p][x]=nq;
                p=pre[p];
            }
        }
    }
    last=np;
}
int quicksortmi(int x,int y){
    if (!y) return 1;
    int t=quicksortmi(x,y/2);
    t=(ll)t*t%mo;
    if (y%2) t=(ll)t*x%mo;
    return t;
}
int main(){
    freopen("knight6.in","r",stdin);
    scanf("%d",&n);
    pre[0]=-1; 
    fo(i,1,n){
        ch=get();
        add(ch-'0');
        ma[last]=mi[last]=i;
    }
    fo(i,1,tot) sum=(sum+step[i]-step[pre[i]])%mo;
    /*fd(i,tot,1){
        mi[pre[i]]=min(mi[pre[i]],mi[i]);
        ma[pre[i]]=max(ma[pre[i]],ma[i]);
        maxs[i]=step[i];
        mins[i]=step[pre[i]]+1;
    }*/
    fo(i,0,tot) cnt[step[i]]++;
    fo(i,0,step[last]) cnt[i]+=cnt[i-1];
    fo(i,0,tot) a[cnt[step[i]]--]=i;
    fd(i,tot+1,1){
        mi[pre[a[i]]]=min(mi[pre[a[i]]],mi[a[i]]);
        ma[pre[a[i]]]=max(ma[pre[a[i]]],ma[a[i]]);
        maxs[a[i]]=step[a[i]];
        mins[a[i]]=step[pre[a[i]]]+1;
    }
    fo(i,1,tot){
        l=mins[i];r=maxs[i];
        l=max(l,ma[i]-mi[i]+1);
        if (l>r) continue;
        ad[ma[i]-r+1]++;
        ad[ma[i]-l+2]--;
        f[mi[i]]-=(r-l+1);
    }
    k=0;
    fo(i,1,n){
        k=(k+ad[i])%mo;
        f[i]=(f[i]+k);
    }
    fo(i,1,n) f[i]=(f[i]+f[i-1])%mo;
    fo(i,1,n) f[i]=sum-f[i];
    fo(i,1,n-1) ans=(ans+(ll)f[i]*quicksortmi(100013,n-i-1)%mo)%mo;
    (ans+=mo)%=mo;
    printf("%d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值