一个串的不同子串数量&串的后面添加操作

题目:https://www.acwing.com/problem/content/2574/

题意:首先给一个空串s,然后每次往串的后面添加一个数字,问每次添加完后,不同的子串的数量是多少,总共添加n次,所以要输出n行结果。

题解:首先考虑一个固定的串,有多少个不同的子串,因为所有的子串就是所有的后缀的所有前缀,那么求出后缀数组,每一个sa[i]和上面不同前缀的数量就是和前面每一个sa[i-1],sa[i-2],sa[i-3]等等的最大公共前缀,然后用len[i]减去这个值就是答案,然后又因为,这个值其实就是height[i]的值(仔细想想就可以理解),那么其实答案就是所有的len[i]-height[i],考虑添加操作,往后面添加会导致每一个后缀都会改变,又因为,一个串的正序和逆序的不同子串数量是相同的,那么就把这个序列倒过来,变成往前添加,然后添加过程不好操作,可以先把所有信息读入,求出长度为n的后缀数组,然后从小到大删数,每次删数,更改height的信息即可,又因为删掉height[i],更新信息height[i+1]=min(height[i],height[i+1])

#include <bits/stdc++.h>
//#define int long long
#define pb push_back
#define pii pair<int, int>
#define mpr make_pair
#define ms(a, b) memset((a), (b), sizeof(a))
#define x first
#define y second
typedef long long ll;
typedef unsigned long long LL;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
using namespace std;
const int N = 1e6+9;

int n,m; 
int s[N];
int sa[N],x[N],y[N],c[N],rk[N],height[N];
int u[N],d[N];//双链表存height关系
ll ans[N];
int get(int x){
    static unordered_map<int,int>hash;
    if(hash.count(x)==0)hash[x]=++m;
    return hash[x];
}
//后缀数组模板
void get_sa(){
    for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
    for(int i=2;i<=m;i++)c[i]+=c[i-1];
    for(int i=n;i;i--)sa[c[x[i]]--]=i;
    for(int k=1;k<=n;k<<=1){
        int num=0;
        for(int i=n-k+1;i<=n;i++)y[++num]=i;
        for(int i=1;i<=n;i++)
            if(sa[i]>k)y[++num]=sa[i]-k;
        for(int i=1;i<=m;i++)c[i]=0;
        for(int i=1;i<=n;i++)c[x[i]]++;
        for(int i=2;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
        swap(x,y);
        x[sa[1]]=1,num=1;
        for(int i=2;i<=n;i++)
            x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num: ++num;
        if(num==n)break;
        m=num;

    }
}
void get_height(){
    for(int i=1;i<=n;i++)rk[sa[i]]=i;
    for(int i=1,k=0;i<=n;i++){
        if(rk[i]==1)continue;
        if(k)k--;
        int j=sa[rk[i]-1];
        while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])k++;
        height[rk[i]]=k;
    }
}

signed main() {
    
    scanf("%d",&n);
    for(int i=n;i;i--)scanf("%d",&s[i]),s[i]=get(s[i]);
    get_sa();
    get_height();
    ll res=0;
    for(int i=1;i<=n;i++){
        res+=n-sa[i]+1-height[i];//得到长度为n的答案
        u[i]=i-1,d[i]=i+1;
    }
    d[0]=1,u[n+1]=n;
    for(int i=1;i<=n;i++){
        ans[i]=res;
        int k=rk[i],j=d[k];
        res-=n-sa[k]+1-height[k];//更新答案
        res-=n-sa[j]+1-height[j];
        height[j]=min(height[j],height[k]);//更新信息
        res += n - sa[j] + 1 - height[j];   //更新答案
        d[u[k]]=d[k],u[d[k]]=u[k];//双链表删数
    }

    for(int i=n;i;i--)printf("%lld\n",ans[i]);
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值