Musical Theme(后缀自动机)

Musical Theme

Time Limit: 1000MS
Memory Limit: 30000K

题目大意:
给定一个串,找出满足条件最长子串:
1,长度大于等于5
2,至少出现两次
3,至少有两个出现位置不重叠

子串不一定要严格相等,两两差值相等即可。
例如:
1 2 3 4 6 = 11 12 13 14 16

n200001ai88 n ≤ 20000 , 1 ≤ a i ≤ 88

Sample Input
30
25 27 30 34 39 45 52 60 69 79 69 60 52 45 39 34 30 26 22 18 82 78 74 70 66 67 64 60 65 80
0
Sample Output
5













<script type="math/tex" id="MathJax-Element-373"> </script>

解:

每个位置作差插入后缀自动机。找长度大于等于5并且出现至少两次的子串。
如何满足条件不重叠?
可以发现一个性质:一个点的right集合为所有parent链指向它的点的并集。初始非复制节点的right集合大小为1,可以知道right的位置,对于每个节点记录right集合的最大和最小位置。拓扑序上推就行。
每个点的答案就是max-min和len+1取min。
因为作差后子串不重叠不一定原串不重叠,随便写一个串可以看出来。

本题重要理解:一个点的right集合为所有fail链指向它的点的并集,大小为它们的和!!!

code:
本来以为转移多了要用map,节省空间,结果TLE了。。。
改成暴力数组存就过了?

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct lxy{
    int p,len,min,max;
    int to[180];
}a[40005];

int n,cnt=1,las=1,ans;
int data[20005];
int tax[20005],tp[40005];

void insert(int c,int w){
    a[++cnt].len=w;a[cnt].min=w,a[cnt].max=w;
    int i;for(i=las;a[i].to[c]==0&&i!=0;i=a[i].p) a[i].to[c]=cnt;
    las=cnt;
    if(i==0){
        a[las].p=1;return;
    }
    int q=a[i].to[c],nq;
    if(a[i].len+1==a[q].len){
        a[las].p=q;return;
    }
    nq=++cnt;
    a[nq]=a[q];a[nq].len=a[i].len+1;
    a[nq].min=10000000;a[nq].max=0;
    for(int j=i;a[j].to[c]==q;j=a[j].p) a[j].to[c]=nq;
    a[q].p=nq;a[las].p=nq;
}

void findtp(){
    for(int i=1;i<=cnt;i++) tax[a[i].len]++;
    for(int i=1;i<=n;i++) tax[i]+=tax[i-1];
    for(int i=1;i<=cnt;i++) tp[tax[a[i].len]--]=i;
}

void readit(int &x){
    x=0;char g=getchar();
    while(g<'0'||g>'9') g=getchar();
    while(g>='0'&&g<='9') x=x*10+g-'0',g=getchar();
}

int main()
{
    while(scanf("%d",&n)){
        if(n==0) break;
        for(int i=1;i<=n;i++)
          readit(data[i]);
        for(int i=1;i<n;i++)
          insert(data[i+1]-data[i]+88,i);
        findtp();
        for(int i=cnt;i>=1;i--){
            int k=min(a[tp[i]].len+1,a[tp[i]].max-a[tp[i]].min);
            if(k>=5) ans=max(ans,k);
            a[a[tp[i]].p].max=max(a[a[tp[i]].p].max,a[tp[i]].max);
            a[a[tp[i]].p].min=min(a[a[tp[i]].p].min,a[tp[i]].min);
        }
        printf("%d\n",ans);
        memset(a,0,sizeof(a));
        for(int i=1;i<=n;i++) tax[i]=0;
        for(int i=1;i<=cnt;i++) tp[i]=0;
        cnt=1,las=1,ans=0;
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值