首页 > 其他分享 >SAM杂题

SAM杂题

时间:2023-02-01 19:22:06浏览次数:35  
标签:cnt last SAM int len fa include 杂题

现在是 SAM 什么都不会了的状态。所以打算搞字符串。差不多就是从头开始学 SAM,但是全是题。

我觉得得一半多都是代码。

P2408 不同子串个数

回忆一下 SAM 中每个节点的子串个数就是 \(len_x-len_{fa_x}\)。加起来就好。也可以是到达每个节点的路径数,可以在 DAG 上计数,最后减掉一个空串。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int n;
long long ans;
char s[100010];
struct SAM{
    int cnt,last,trie[200010][26],fa[200010],len[200010];
    SAM(){cnt=1,last=1;}
    void ins(char ch){
        int p=last;last=++cnt;
        len[last]=len[p]+1;
        while(p&&!trie[p][ch])trie[p][ch]=last,p=fa[p];
        if(p==0){
            fa[last]=1;ans+=(len[last]-len[fa[last]]);return;
        }
        int q=trie[p][ch];
        if(len[p]+1==len[q]){
            fa[last]=q;ans+=(len[last]-len[fa[last]]);return;
        }
        len[++cnt]=len[p]+1;
        for(int i=0;i<26;i++)trie[cnt][i]=trie[q][i];
        fa[cnt]=fa[q];fa[q]=cnt;fa[last]=cnt;
        while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
        ans+=(len[last]-len[fa[last]]);
    }
}sam;
int main(){
    scanf("%d%s",&n,s+1);
    for(int i=1;i<=n;i++)sam.ins(s[i]-'a');
    printf("%lld\n",ans);
}

P3804 【模板】后缀自动机 (SAM)

首先每个节点的 endpos 集合大小就是这个节点包含的所有子串的出现次数。然后回忆另一个结论:如果把每个前缀赋予 \(1\) 的权值,那么每个节点的 endpos 集合就是子树和。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
char s[1000010];
int n;
long long ans;
struct node{
    int v,next;
}edge[2000010];
int head[2000010],t;
void add(int u,int v){
    edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
int cnt,last,len[2000010],size[2000010],fa[2000010],trie[2000010][26];
void build(){
    cnt=last=1;
    int p;
    for(int i=1;i<=n;i++){
        p=last;last=++cnt;
        size[last]=1;len[last]=len[p]+1;
        while(p&&!trie[p][s[i]-'a'])trie[p][s[i]-'a']=cnt,p=fa[p];
        if(!p){
            fa[last]=1;continue;
        }
        int q=trie[p][s[i]-'a'];
        if(len[p]+1==len[q]){
            fa[last]=q;continue;
        }
        len[++cnt]=len[p]+1;
        for(int j=0;j<26;j++)trie[cnt][j]=trie[q][j];
        fa[cnt]=fa[q];fa[q]=cnt;fa[last]=cnt;
        for(int j=p;trie[j][s[i]-'a']==q;j=fa[j])trie[j][s[i]-'a']=cnt;
    }
}
void dfs(int x){
    for(int i=head[x];i;i=edge[i].next){
        dfs(edge[i].v);size[x]+=size[edge[i].v];
    }
    if(size[x]!=1)ans=max(ans,1ll*size[x]*len[x]);
}
int main(){
    scanf("%s",s+1);n=strlen(s+1);
    build();
    for(int i=2;i<=cnt;i++)add(fa[i],i);
    dfs(1);
    printf("%lld\n",ans);
    return 0;
}

[TJOI2015]弦论

首先我们可以类似线段树上二分的在 SAM 上跑出字符串。这样我们就需要找到每次走到一个节点,字典序比答案串小的串的个数。

在 SAM 上跑 dp,每个节点的答案是所有子节点之和。初值的问题,如果 \(t=0\),相同子串算一次,那么就仍然是 \(1\)。如果 \(t=1\),那么初值就是 endpos 集合大小。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
char s[500010];
int n,k,t[1000010],size[1000010],a[1000010];
long long ans;
int cnt,last,trie[1000010][26],len[1000010],fa[1000010],sum[1000010];
void ins(char ch){
    int p=last;last=++cnt;
    len[last]=len[p]+1;size[last]=1;
    while(p&&!trie[p][ch])trie[p][ch]=cnt,p=fa[p];
    if(!p){
        fa[last]=1;return;
    }
    int q=trie[p][ch];
    if(len[p]+1==len[q]){
        fa[last]=q;return;
    }
    len[++cnt]=len[p]+1;
    for(int j=0;j<26;j++)trie[cnt][j]=trie[q][j];
    fa[cnt]=fa[q];fa[q]=cnt;fa[last]=cnt;
    while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
}
void print(int x,int k){
    if(k<=size[x])return;
    k-=size[x];
    for(int i=0;i<26;i++){
        if(!trie[x][i])continue;
        if(k>sum[trie[x][i]]){
            k-=sum[trie[x][i]];continue;
        }
        putchar(i+'a');
        print(trie[x][i],k);
        return;
    }
}
int main(){
    int od;cnt=last=1;
    scanf("%s%d%d",s+1,&od,&k);n=strlen(s+1);
    for(int i=1;i<=n;i++)ins(s[i]-'a');
    for(int i=1;i<=cnt;i++)t[len[i]]++;
    for(int i=1;i<=cnt;i++)t[i]+=t[i-1];
    for(int i=1;i<=cnt;i++)a[t[len[i]]--]=i;
    for(int i=cnt;i>=1;i--)size[fa[a[i]]]+=size[a[i]];
    for(int i=2;i<=cnt;i++){
        if(od==0)size[i]=1;
        sum[i]=size[i];
    }
    size[1]=sum[1]=0;
    for(int i=cnt;i>=1;i--){
        for(int j=0;j<26;j++){
            if(trie[a[i]][j])sum[a[i]]+=sum[trie[a[i]][j]];
        }
    }
    cerr<<sum[1]<<endl;
    if(sum[1]<k){
        puts("-1");return 0;
    }
    print(1,k);
    return 0;
}

SP1812 LCS2 - Longest Common Substring II

先看看这道题的削弱版,只有两个串。我们可以建出一个的 SAM,让另一个在上面跑,处理出每个位置的最大匹配长度。答案就是它们的最大值。

这个题类似,可以对除最后一个串的所有串各建一个 SAM,然后让最后一个串分别在上面跑,把最大匹配长度取 \(\min\) 再统计答案就行了。

也可以广义 SAM,预处理出广义 SAM 的哪些节点是所有字符串共有的。这个可以建完 SAM 后把每个字符串扔到上面跑一遍,对于每个字符串分别求出 endpos 集合大小,若都不为 \(0\) 则是共有的。然后在共有的节点上随便找个字符串跑一下就行了。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int n,m,ans,lst[100010];
char s[11][100010];
struct SAM{
    int cnt,last,trie[200010][26],fa[200010],len[200010],size[200010];
    SAM(){cnt=1,last=1;}
    void ins(char ch){
        int p=last;last=++cnt;
        len[last]=len[p]+1;size[last]=1;
        while(p&&!trie[p][ch])trie[p][ch]=last,p=fa[p];
        if(p==0){
            fa[last]=1;return;
        }
        int q=trie[p][ch];
        if(len[p]+1==len[q]){
            fa[last]=q;return;
        }
        len[++cnt]=len[p]+1;
        for(int i=0;i<26;i++)trie[cnt][i]=trie[q][i];
        fa[cnt]=fa[q];fa[q]=cnt;fa[last]=cnt;
        while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
    }
    void query(char s[]){
        int p=1,l=0;
        for(int i=1;i<=n;i++){
            while(p!=1&&!trie[p][s[i]-'a'])p=fa[p],l=len[p];
            if(trie[p][s[i]-'a'])p=trie[p][s[i]-'a'],l++;
            lst[i]=min(lst[i],l);
        }
    }
}sam[11];
int main(){
    int pos=1;
    while(~scanf("%s",s[pos]+1))pos++;
    pos--;
    for(int i=1;i<pos;i++){
        int n=strlen(s[i]+1);
        for(int j=1;j<=n;j++)sam[i].ins(s[i][j]-'a');
    }
    n=strlen(s[pos]+1);
    for(int i=1;i<=n;i++)lst[i]=0x3f3f3f3f;
    for(int i=1;i<pos;i++)sam[i].query(s[pos]);
    for(int i=1;i<=n;i++)ans=max(ans,lst[i]);
    printf("%d\n",ans);
    return 0;
}

[BJOI2020] 封印

不错的题,就是作为 A 队加试有点水)

首先仍然建出 \(t\) 的 SAM,并让 \(s\) 在上面跑出最大匹配长度 \(ans\)。考虑怎么快速解决询问。

发现每个位置 \(i\) 的答案是 \(\min(i-l+1,ans_i)\),那么位置每次向右走一个, \(i-l+1\) 增加 \(1\) ,而 \(ans_i\) 至多增加 \(1\)。容易发现 \(ans_i-(i-l+1)\) 单调不增,因此可以二分零点。

找到零点 \(pos\) 之后左边的答案自然是 \(pos-l\),右边就是个区间 \(ans\) 的最大值,可以 ST 表。

二分的时候注意特判全段都小于 \(0\) 的情况,应当返回 \(r+1\)。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int n,m,ans[200010];
char s[200010],t[200010];
int cnt=1,last=1,trie[400010][26],fa[400010],len[400010];
void ins(char ch){
    int p=last;last=++cnt;
    len[last]=len[p]+1;
    while(p&&!trie[p][ch])trie[p][ch]=last,p=fa[p];
    if(p==0){
        fa[last]=1;return;
    }
    int q=trie[p][ch];
    if(len[p]+1==len[q]){
        fa[last]=q;return;
    }
    len[++cnt]=len[p]+1;
    for(int i=0;i<26;i++)trie[cnt][i]=trie[q][i];
    fa[cnt]=fa[q];fa[q]=cnt;fa[last]=cnt;
    while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
}
void qry(char s[]){
    int p=1,l=0;
    for(int i=1;i<=n;i++){
        while(p!=1&&!trie[p][s[i]-'a'])p=fa[p],l=len[p];
        if(trie[p][s[i]-'a'])p=trie[p][s[i]-'a'],l++;
        ans[i]=l;
    }
}
int st[200010][20];
int query(int l,int r){
    if(l>r)return 0;
    int k=__lg(r-l+1);
    return max(st[l][k],st[r-(1<<k)+1][k]);
}
int getans(int l,int r){
    int L=l,R=r;
    while(L<R){
        int mid=(L+R)>>1;
        if(ans[mid]<=mid-l+1)R=mid;
        else L=mid+1;
    }
    if(L-l+1<ans[L])L++;
    return L;
}
int main(){
    scanf("%s%s",s+1,t+1);
    n=strlen(s+1);m=strlen(t+1);
    for(int i=1;i<=m;i++)ins(t[i]-'a');
    qry(s);
    for(int i=1;i<=n;i++)st[i][0]=ans[i];
    for(int j=1;j<=__lg(n);j++){
        for(int i=1;i+(1<<j)-1<=n;i++){
            st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
        }
    }
    int q;scanf("%d",&q);
    while(q--){
        int l,r;scanf("%d%d",&l,&r);
        int pos=getans(l,r);
        printf("%d\n",max(query(pos,r),pos-l));
    }
    return 0;
}

[TJOI2019]甲苯先生和大中锋的字符串

把 endpos 找出来然后就是个区间加最后单点查,实际上差分就行。代码不上了。

CF235C Cyclical Quest

(WJMZBMR)

循环同构可以变成前面去掉一个字符后面加上一个字符。后面加上就是在 SAM 里跑转移边,前面去掉就是在 parent 树上跳父亲。相同的子串只能算一次,那么每个位置打个标记就行了。

写起来挺顺手的。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int n,m,ans;
char s[1000010];
struct node{
    int v,next;
}edge[2000010];
int t,head[2000010];
void add(int u,int v){
    edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
int cnt=1,last=1,trie[2000010][26],fa[2000010],len[2000010],siz[2000010];
int v[2000010];
void ins(int ch){
    int p=last;last=++cnt;
    len[last]=len[p]+1;siz[last]++;
    while(p&&!trie[p][ch])trie[p][ch]=cnt,p=fa[p];
    if(!p){
        fa[last]=1;return;
    }
    int q=trie[p][ch];
    if(len[p]+1==len[q]){
        fa[last]=q;return;
    }
    len[++cnt]=len[p]+1;
    for(int i=0;i<26;i++)trie[cnt][i]=trie[q][i];
    fa[cnt]=fa[q];fa[q]=cnt;fa[last]=cnt;
    while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
}
void dfs(int x){
    for(int i=head[x];i;i=edge[i].next){
        dfs(edge[i].v);siz[x]+=siz[edge[i].v];
    }
}
void query(char s[],int id){
    int p=1,l=0;
    for(int i=1;i<=n;i++){
        while(p!=1&&!trie[p][s[i]-'a'])p=fa[p],l=len[p];
        if(trie[p][s[i]-'a'])p=trie[p][s[i]-'a'],l++;
    }
    for(int i=1;i<=n;i++){
        if(l==n){
            if(v[p]!=id)ans+=siz[p];
            v[p]=id;l--;
            if(l==len[fa[p]])p=fa[p];
        }
        while(p!=1&&!trie[p][s[i]-'a'])p=fa[p],l=len[p];
        if(trie[p][s[i]-'a'])p=trie[p][s[i]-'a'],l++;
    }
}
int main(){
    scanf("%s",s+1);n=strlen(s+1);
    for(int i=1;i<=n;i++)ins(s[i]-'a');
    for(int i=2;i<=cnt;i++)add(fa[i],i);
    dfs(1);
    scanf("%d",&m);
    while(m--){
        scanf("%s",s+1);n=strlen(s+1);ans=0;
        query(s,m+1);
        printf("%d\n",ans);
    }
}

[HEOI2016/TJOI2016]字符串

之前题库暴力艹过去了,现在重新写一遍。用 SAM。

二分答案,于是变成 \(s[c,c+mid-1]\) 在 \(s[a,b]\) 是否出现过。从 \(s[1,c+mid-1]\) 倍增跳到 \(s[c,c+mid-1]\),然后线段树合并维护每个节点的 endpos 即可。复杂度好像是 \(O(n\log^2n)\)。

另外注意线段树合并的时候一定要新开节点。别的其实不是很难写。

一个类似的题是 CF666E,对 \(S\) 建 SAM 然后把所有 \(T\) 扔上去跑并标记出现次数,线段树合并就行了。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define lson tree[rt].ls
#define rson tree[rt].rs
using namespace std;
struct node{
    int v,next;
}edge[200010];
int tot,head[200010];
void add(int u,int v){
    edge[++tot].v=v;edge[tot].next=head[u];head[u]=tot;
}
int n,m,t,rt[200010];
char s[100010];
struct tr{
    int ls,rs,sum;
}tree[200010<<5];
void pushup(int rt){
    tree[rt].sum=tree[lson].sum+tree[rson].sum;
}
void update(int &rt,int L,int R,int pos){
    if(!rt)rt=++t;
    if(L==R){
        tree[rt].sum++;return;
    }
    int mid=(L+R)>>1;
    if(pos<=mid)update(lson,L,mid,pos);
    else update(rson,mid+1,R,pos);
    pushup(rt);
}
int query(int rt,int L,int R,int l,int r){
    if(l<=L&&R<=r)return tree[rt].sum;
    int mid=(L+R)>>1,val=0;
    if(l<=mid)val+=query(lson,L,mid,l,r);
    if(mid<r)val+=query(rson,mid+1,R,l,r);
    return val;
}
int merge(int x,int y,int l,int r){
    if(!x||!y)return x|y;
    int rt=++t;
    if(l==r){
        tree[rt].sum=tree[x].sum+tree[y].sum;
        return rt;
    }
    int mid=(l+r)>>1;
    tree[rt].ls=merge(tree[x].ls,tree[y].ls,l,mid);
    tree[rt].rs=merge(tree[x].rs,tree[y].rs,mid+1,r);
    pushup(rt);
    return rt;
}
struct SAM{
    int cnt,last,trie[200010][26],fa[200010],len[200010];
    SAM(){cnt=last=1;}
    void ins(int ch){
        int p=last;last=++cnt;
        len[last]=len[p]+1;
        while(p&&!trie[p][ch])trie[p][ch]=cnt,p=fa[p];
        if(!p){
            fa[last]=1;return;
        }
        int q=trie[p][ch];
        if(len[p]+1==len[q]){
            fa[last]=q;return;
        }
        len[++cnt]=len[p]+1;
        for(int i=0;i<26;i++)trie[cnt][i]=trie[q][i];
        fa[cnt]=fa[q];fa[q]=cnt;fa[last]=cnt;
        while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
    }
}sam;
int fa[200010][20],id[100010];
void dfs(int x){
    for(int i=head[x];i;i=edge[i].next){
        fa[edge[i].v][0]=x;
        for(int j=1;j<=__lg(sam.cnt);j++)fa[edge[i].v][j]=fa[fa[edge[i].v][j-1]][j-1];
        dfs(edge[i].v);
        rt[x]=merge(rt[x],rt[edge[i].v],1,n);
    }
}
bool check(int mid,int a,int b,int c,int d){
    int x=id[c+mid-1];
    for(int i=__lg(sam.cnt);i>=0;i--){
        if(sam.len[fa[x][i]]>=mid)x=fa[x][i];
    }
    return query(rt[x],1,n,a+mid-1,b);
}
int main(){
    scanf("%d%d%s",&n,&m,s+1);
    id[0]=1;
    for(int i=1;i<=n;i++){
        sam.ins(s[i]-'a');
        id[i]=sam.last;
        update(rt[id[i]],1,n,i);
    }
    for(int i=2;i<=sam.cnt;i++)add(sam.fa[i],i);
    dfs(1);
    while(m--){
        int a,b,c,d;scanf("%d%d%d%d",&a,&b,&c,&d);
        int l=0,r=min(b-a+1,d-c+1);
        while(l<r){
            int mid=(l+r+1)>>1;
            if(check(mid,a,b,c,d))l=mid;
            else r=mid-1;
        }
        printf("%d\n",l);
    }
    return 0;
}

[CTSC2012]熟悉的文章

首先显然可以二分答案。考虑怎么验证。

一个 dp:设 \(dp_i\) 是到 \(i\) 能匹配的最大长度,\(l_i\) 为到 \(i\) 在广义 SAM 上能匹配到的长度。那么有:

\[dp_i=\max(dp_{i-1},dp_j+i-j,j\in[i-l_i,i-mid]) \]

发现 \(i-mid\) 单增,\(i-l_i\) 单调不降,单调队列。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
char s[1100010];
int n,m,len,dp[1100010],match[1100010];
struct Sam{
    int cnt,fa[2200010],len[2200010],trie[2200010][2];
    Sam(){cnt=1;}
    int ins(int ch,int last){
        if(trie[last][ch]){
            int p=last,q=trie[p][ch];
            if(len[p]+1==len[q])return q;
            else{
                len[++cnt]=len[p]+1;
                for(int i=0;i<2;i++)trie[cnt][i]=trie[q][i];
                fa[cnt]=fa[q];fa[q]=cnt;
                while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
                return cnt;
            }
        }
        int p=last;last=++cnt;
        len[last]=len[p]+1;
        while(p&&!trie[p][ch])trie[p][ch]=cnt,p=fa[p];
        if(!p){
            fa[last]=1;return last;
        }
        int q=trie[p][ch];
        if(len[p]+1==len[q]){
            fa[last]=q;return last;
        }
        len[++cnt]=len[p]+1;
        for(int i=0;i<2;i++)trie[cnt][i]=trie[q][i];
        fa[cnt]=fa[q];fa[q]=cnt;fa[last]=cnt;
        while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
        return last;
    }
    void getmatch(char s[]){
        int slen=strlen(s+1),p=1,l=0;
        for(int i=1;i<=slen;i++){
            while(p&&!trie[p][s[i]-'0'])p=fa[p],l=len[p];
            if(p)p=trie[p][s[i]-'0'],l++;
            else p=1,l=0;
            match[i]=l;
        }
    }
}SAM;
int q[1100010],L,R,last;
bool check(int mid){
    L=1,R=0;
    for(int i=1;i<mid;i++)dp[i]=0;
    for(int i=mid;i<=len;i++){
        dp[i]=dp[i-1];
        while(L<=R&&dp[q[R]]-q[R]<dp[i-mid]-(i-mid))R--;
        q[++R]=i-mid;
        while(L<=R&&q[L]<i-match[i])L++;
        if(L<=R)dp[i]=max(dp[i],dp[q[L]]-q[L]+i);
    }
    return dp[len]*10>=len*9;
}
signed main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%s",s+1);
        last=1;len=strlen(s+1);
        for(int j=1;j<=len;j++)last=SAM.ins(s[j]-'0',last);
    }
    while(n--){
        scanf("%s",s+1);
        int l=1,r=len=strlen(s+1);
        SAM.getmatch(s);
        while(l<r){
            int mid=(l+r+1)>>1;
            if(check(mid))l=mid;
            else r=mid-1;
        }
        printf("%d\n",l);
    }
    return 0;
}

[NOI2018] 你的名字

好强。

首先题意翻译成人话是找到 \(T\) 的所有本质不同子串中不是 \(S[l,r]\) 子串的个数。

先考虑一下不带 \(l,r\) 的情况。考虑 \(T\) 的 SAM 上每个节点的贡献。对于这个节点的一个 endpos \(i\),那么假设 \(T\) 的 \(x\) 位置能和 \(S\) 匹配的最长长度是 \(match_x\),那么这个节点所有大于 \(match_i\) 的子串都不能与 \(S\) 匹配。为了方便直接找 firstpos 即可。于是一个节点的贡献就 \(\max(0,len_x-\max(len_{fa_x},match_{firstpos_x}))\)。

然后带上 \(l,r\) 的限制,那么只要找这一段最前面的 \(endpos\) ,求 \(match\) 的时候只找 endpos 在范围内的即可。

注意求 \(match\) 的时候不能直接跳 \(fa\),因为匹配长度减小之后 endpos 的合法范围增大,还要继续判断,一直到当前的长度不在该节点的 \(len\) 范围内再往上跳。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define lson tree[rt].ls
#define rson tree[rt].rs
using namespace std;
struct node{
    int v,next;
}edge[2000010];
int tot,head[2000010];
void add(int u,int v){
    edge[++tot].v=v;edge[tot].next=head[u];head[u]=tot;
}
int n,m,t,rt[2000010];
char s[1000010];
struct tr{
    int ls,rs,sum;
}tree[2000010<<5];
void pushup(int rt){
    tree[rt].sum=tree[lson].sum+tree[rson].sum;
}
void update(int &rt,int L,int R,int pos){
    if(!rt)rt=++t;
    if(L==R){
        tree[rt].sum++;return;
    }
    int mid=(L+R)>>1;
    if(pos<=mid)update(lson,L,mid,pos);
    else update(rson,mid+1,R,pos);
    pushup(rt);
}
int query(int rt,int L,int R,int l,int r){
    if(!rt)return 0;
    if(l<=L&&R<=r)return tree[rt].sum;
    int mid=(L+R)>>1,val=0;
    if(l<=mid)val+=query(lson,L,mid,l,r);
    if(mid<r)val+=query(rson,mid+1,R,l,r);
    return val;
}
int merge(int x,int y,int l,int r){
    if(!x||!y)return x|y;
    int rt=++t;
    if(l==r){
        tree[rt].sum=tree[x].sum+tree[y].sum;
        return rt;
    }
    int mid=(l+r)>>1;
    tree[rt].ls=merge(tree[x].ls,tree[y].ls,l,mid);
    tree[rt].rs=merge(tree[x].rs,tree[y].rs,mid+1,r);
    pushup(rt);
    return rt;
}
int match[200010];
struct SAM{
    int cnt,last,trie[2000010][26],fa[2000010],len[2000010],firstpos[2000010];
    SAM(){cnt=last=1;}
    void ins(int ch,int id){
        int p=last;last=++cnt;
        len[last]=len[p]+1;firstpos[last]=id;
        while(p&&!trie[p][ch])trie[p][ch]=cnt,p=fa[p];
        if(!p){
            fa[last]=1;return;
        }
        int q=trie[p][ch];
        if(len[p]+1==len[q]){
            fa[last]=q;return;
        }
        len[++cnt]=len[p]+1;firstpos[cnt]=firstpos[q];
        for(int i=0;i<26;i++)trie[cnt][i]=trie[q][i];
        fa[cnt]=fa[q];fa[q]=cnt;fa[last]=cnt;
        while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
    }
}sam,sam2;
void dfs(int x){
    for(int i=head[x];i;i=edge[i].next){
        dfs(edge[i].v);rt[x]=merge(rt[x],rt[edge[i].v],1,n);
    }
}
void get(int l,int r){
    int p=1,L=0;
    for(int i=1;i<=m;i++){
        while(1){
            if(sam.trie[p][s[i]-'a']&&query(rt[sam.trie[p][s[i]-'a']],1,n,l+L,r)){
                p=sam.trie[p][s[i]-'a'];L++;
                break;
            }
            if(!L)break;
            L--;
            if(L==sam.len[sam.fa[p]])p=sam.fa[p];
        }
        match[i]=L;
    }
}
int main(){
    scanf("%s",s+1);n=strlen(s+1);
    for(int i=1;i<=n;i++){
        sam.ins(s[i]-'a',i);
        update(rt[sam.last],1,n,i);
    }
    for(int i=2;i<=sam.cnt;i++)add(sam.fa[i],i);
    dfs(1);
    int q;scanf("%d",&q);
    while(q--){
        int l,r;scanf("%s%d%d",s+1,&l,&r);
        m=strlen(s+1);
        long long ans=0;
        for(int i=1;i<=m;i++)sam2.ins(s[i]-'a',i);
        get(l,r);
        for(int i=2;i<=sam2.cnt;i++)ans+=max(0,sam2.len[i]-max(match[sam2.firstpos[i]],sam2.len[sam2.fa[i]]));
        printf("%lld\n",ans);
        for(int i=1;i<=sam2.cnt;i++){
            sam2.len[i]=sam2.fa[i]=sam2.firstpos[i]=0;
            for(int j=0;j<26;j++)sam2.trie[i][j]=0;
        }
        sam2.cnt=sam2.last=1;
    }
    return 0;
}

先就这样发出来。

标签:cnt,last,SAM,int,len,fa,include,杂题
From: https://www.cnblogs.com/gtm1514/p/17083926.html

相关文章

  • Mitigating Propagation failures in PINNs using Evolutionary Sampling
    未发表   本文采用进化采样策略来缓解PINN使用中的传播失败。大体可以描述为一类主动学习/自步学习方法与分析。当前,面对PINN失败的问题,当前的研究大部分都集中在......
  • Pandas resample数据重采样
    随机抽样,是统计学中常用的一种方法,它可以帮助我们从大量的数据中快速地构建出一组数据分析模型。在Pandas中,如果想要对数据集进行随机抽样,需要使用sample()函数。samp......
  • 第六十一章 使用 ^PERFSAMPLE 监控进程 - 分析维度
    第六十一章使用^PERFSAMPLE监控进程-分析维度分析维度^PERFSAMPLE工具中描述了分析的维度。本节提供了一些附加信息。cpu-使用CPU?(进程状态表示预期的CPU......
  • gym102916 XXI Open Cup, Grand Prix of Samara
    A.Absenteeism这题是想的越多写的代码越少。首先根据题目中的约束,可以弄出一堆矩形,直接线段树加扫描线就行。其实不算难写。开始推性质,找简洁的做法。求的区间为\([x......
  • OnionArch-NorthwindTraders,sample-dotnet-core-cqrs-api
    NorthwindTraders, sample-dotnet-core-cqrs-api 项目OnionArch-采用DDD+CQRS+.Net7.0实现的洋葱架构 博主最近失业在家,找工作之余,看了一些关于洋葱(整洁)架构的资......
  • 网络流杂题+不多的归纳
    照着ppt写博客的时代应是一去不复返了,借不同题目阐述基本思想当为至道。转个链接:https://www.cnblogs.com/SYCstudio/p/7260613.html,这篇笔记写得很好。引入题目之前,阐......
  • 第六十章 使用 ^PERFSAMPLE 监控进程 - 预定义分析示例
    第六十章使用^PERFSAMPLE监控进程-预定义分析示例预定义分析示例以下是从过程状态维度开始的分析示例。在此示例中,^PERFSAMPLE在319994个样本中找到了76755个......
  • 样本熵(SampEn)的C/C++代码实现与优化
    样本熵(SampEn)的C/C++代码实现与优化本文不介绍什么是样本熵,具体推荐看此文https://blog.csdn.net/Cratial/article/details/79742363,写的很好,里面的示例也被我拿来测试......
  • SAM
    #include<bits/stdc++.h>#defineintlonglongusingnamespacestd;constintN=2e6+10;intread(){ intx=0,f=1; charch=getchar(); while(ch<48||ch>57)......
  • 全志V系列芯片如何实现mpp sample外编?
    1.主题V系列多媒体mppsample外编方法2.问题背景用户希望TinaSDK能提供编译工具链,多媒体库和头文件,使得自己编写的应用能编写Makefile去链接多媒体库来编译生成app应......