[BZOJ1396&2865]识别子串

bzoj1396
bzoj2865
dbzoj1396
dbzoj2865

题面

XX在进行字符串研究的时候,遇到了一个十分棘手的问题。
在这个问题中,给定一个字符串\(S\),与一个整数\(K\),定义\(S\)的子串\(T=S(i,j)\)是关于第\(K\)位的识别子串,满足以下两个条件:
1、\(i≤K≤j\)
2、子串\(T\)只在\(S\)中出现过一次。
例如,\(S=banana\)\(K=5\),则关于第K位的识别子串有\(nana\)\(anan\)\(anana\)\(nan\)\(banan\)\(banana\)
现在,给定\(S\),XX希望知道对于\(S\)的每一位,最短的识别子串长度是多少,请你来帮助他。

sol

子串\(T\)只在\(S\)中出现过一次?那不就是\(|endpos|=1\)
所以说怎么求\(endpos\)呢?
其实,对于\(|endpos|=1\)的点来说,他的\(len\)就是\(endpos\)
而且一个\(|endpos|=1\)的点是一定没有子节点的,所以很容易就可以找出所有的这种点。
假设我们已经找出了一个\(|endpos|=1\)的点\(i\),那么以这个点为结束位置的且在原串中只出现一次的子串的长度范围就是\([len_{fa_i}+1,len_i]\)
考虑他对每个位置的贡献。记\(l=len_i-len_{fa_i},r=len_i\),那么对于区间\([l,r]\),获得的贡献是\(len_{fa_i}+1\),也就是满足长度范围的最小值。
而对于区间\([1,l-1]\),由于要保证\(i\le K \le j\)的限制,所以获得的贡献会是一个等差数列。
最后的答案就是每个位置上的最小值。
等差数列怎么维护?开两棵线段树其中一棵减个下标就好了。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1e6+5;
int n,last=1,tot=1,tr[N][26],fa[N],len[N],fg[N];
char s[N];
struct segment_tree{
    int t[N<<1];
    void init(){memset(t,63,sizeof(t));}
    void modify(int x,int l,int r,int ql,int qr,int v)
        {
            if (ql>qr) return;
            if (l>=ql&&r<=qr) {t[x]=min(t[x],v);return;}
            int mid=l+r>>1;
            if (ql<=mid) modify(x<<1,l,mid,ql,qr,v);
            if (qr>mid) modify(x<<1|1,mid+1,r,ql,qr,v);
        }
    int query(int x,int l,int r,int p)
        {
            if (l==r) return t[x];
            t[x<<1]=min(t[x<<1],t[x]);t[x<<1|1]=min(t[x<<1|1],t[x]);
            int mid=l+r>>1;
            if (p<=mid) return query(x<<1,l,mid,p);
            else return query(x<<1|1,mid+1,r,p);
        }
}A,B;
void extend(int c)
{
    int v=last,u=++tot;last=u;
    len[u]=len[v]+1;
    while (v&&!tr[v][c]) tr[v][c]=u,v=fa[v];
    if (!v) fa[u]=1;
    else{
        int x=tr[v][c];
        if (len[x]==len[v]+1) fa[u]=x;
        else{
            int y=++tot;
            memcpy(tr[y],tr[x],sizeof(tr[y]));
            fa[y]=fa[x];fa[x]=fa[u]=y;len[y]=len[v]+1;
            while (v&&tr[v][c]==x) tr[v][c]=y,v=fa[v];
        }
    }
}
int main()
{
    scanf("%s",s+1);n=strlen(s+1);
    for (int i=1;i<=n;++i) extend(s[i]-'a');
    for (int i=1;i<=tot;++i) fg[i]=1;
    for (int i=1;i<=tot;++i) fg[fa[i]]=0;
    A.init();B.init();
    for (int i=1;i<=tot;++i)
        if (fg[i])
        {
            int l=len[i]-len[fa[i]],r=len[i];
            A.modify(1,1,n,1,l-1,r+1);
            B.modify(1,1,n,l,r,r-l+1);
        }
    for (int i=1;i<=n;++i)
        printf("%d\n",min(A.query(1,1,n,i)-i,B.query(1,1,n,i)));
    return 0;
}

转载于:https://www.cnblogs.com/zhoushuyu/p/8695309.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值