POJ 1743 Musical Theme【SAM】

POJ1743 Musical Theme

要找长度\(\ge 5\)且出现次数\(\ge 2\)并且第一次出现和最后一次出现不重叠的最长子串。
题目条件中,如果对于两个串,在一个串的每个数上都加上相同的数之后可以得到另一个串,那么这个两个串可以被是相同的。
首先我们先得到差分数组,然后要求的就是差分数组中长度\(\ge 4\)且出现次数\(\ge 2\)并且第一次出现和最后一次出现不重叠的最长子串
我们需要知道的是每个等价类中终点的最左端和最右端的位置,即(\(firstpos,lastpos\)),每次新加入一个字符所得到的等价类其\(firstpos\)\(lastpos\)必然为当前的下标,当构造完\(SAM\)之后,由于\(parent\)树的性质,\(link[u]\)所表示的等价类必然是\(u\)所表示的等价类的后缀,所以可以得到:\(lastpos[u] = max_{v\in children} lastpos[v]\)其中\(v\)\(u\)\(parent\)树中的儿子,而\(firstpos\)必然一直保持不变。
可以用拓扑排序然后倒序遍历来代替建\(parent\)树然后\(dfs\),拓扑排序即为按\(len\)排序,用基数排序即可

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<string>
#include<algorithm>
#include<stack>
using namespace std;
void ____(){ ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0); }
const int MAXN = 1e5+7;
int n,A[MAXN];
struct SAM{
    int len[MAXN],link[MAXN],firstpos[MAXN],lastpos[MAXN],ch[MAXN][180],tot,last,cnt[MAXN],c[MAXN],sa[MAXN];
    void init(){ link[tot = last = cnt[0] = len[0] = 0] = -1; memset(ch[0],0,sizeof(ch[0])); }
    void extend(int x){
        int np = ++tot, p = last; firstpos[tot] = lastpos[tot] = len[tot] = len[last] + 1;
        memset(ch[tot],0,sizeof(ch[tot])); cnt[tot] = 1;
        while(p!=-1 and !ch[p][x]){
            ch[p][x] = np;
            p = link[p];
        }
        if(p==-1) link[np] = 0;
        else{
            int q = ch[p][x];
            if(len[p]+1==len[q]) link[np] = q;
            else{
                int clone = ++tot;
                cnt[clone] = 0;
                firstpos[clone] = firstpos[q];
                lastpos[clone] = lastpos[q];
                len[clone] = len[p] + 1;
                for(int i = 0; i < 180; i++) ch[clone][i] = ch[q][i];
                link[clone] = link[q];
                while(p!=-1 and ch[p][x]==q){
                    ch[p][x] = clone;
                    p = link[p];
                }
                link[np] = link[q] = clone;
            }
        }
        last = np;
    }
    int solve(){
        for(int i = 0; i <= n; i++) c[i] = 0;
        for(int i = 0; i <= tot; i++) c[len[i]]++;
        for(int i = 1; i <= n; i++) c[i] += c[i-1];
        for(int i = tot; i >= 0; i--) sa[c[len[i]]--] = i;
        int ret = 0;
        for(int i = tot+1; i >= 1; i--){            //这里要注意,基数排序的时候是0~tot,所以排名最后的是tot+1
            int u = sa[i];
            cnt[link[u]] += cnt[u];
            lastpos[link[u]] = max(lastpos[link[u]],lastpos[u]);
            ret = max(ret,min(len[u] + 1,lastpos[u]-firstpos[u]));
        }
        if(ret<5) ret = 0;
        return ret;
    }
}sam;
void solve(){
    for(int i = 1; i <= n; i++) scanf("%d",&A[i]);
    for(int i = 1; i < n; i++) A[i] = A[i+1] - A[i] + 88;
    sam.init(); for(int i = 1; i < n; i++) sam.extend(A[i]);
    printf("%d\n",sam.solve());
}
int main(){
    while(scanf("%d",&n)!=EOF and n) solve(); 
    return 0;
}
posted @ 2020-04-14 19:48  _kiko  阅读(92)  评论(0编辑  收藏  举报