【GDOI2017 day1 T3】微信 && SAM学习小记

3 篇文章 0 订阅
1 篇文章 0 订阅

SAM学习小记

【GDOI2017 day1 T3】微信

Description
Description
Input
Input
Output
Output
Sample Input

3
idont<<<<loveyou
i<youloveme
lovingyou
2
110
111

Sample Output

4
3

Data Constraint
Data


一道字符串题,比赛以前,只会KMP,ExKMP,SA,Manachar,Trie。。。一看这道题,放入Trie中,然后呢??


出题人说,用广义后缀自动机。啊啊啊?没学过啊

现在终于学了SAM,再看看这道题——水题!!!


后缀自动机(Suffix Automaton)

后缀自动机是一个用O(n)的复杂度,以遍历DAG的方式表示出一个或多个字符串的所有子串的东东

SAM中元素的一些成分

  • S:原串
  • x:DAG中的x节点其中 rootx 的最长路径为原串x的前缀
  • nxt[x][c]:节点x所代表的串中拓展一个字符c到达的节点
  • len[x]: rootx 的最长路径长度
  • pre[x]:x节点代表的前缀的最长后缀为pre[x]代表的前缀
  • right(s):S中的所有子串s的末尾位置集合
  • 节点x所表示的子串: rootx 的所有路径代表的字符串

构造方式

make

注意根为1

实例:abacb:
①插入a
Step1
②这时las=2,插入b,2的fa链2-1,nxt[2][b]为空,nxt[2][b]=now,跳到1,重复执行操作,跳到0,结束,fa[now]=1
这里写图片描述
③插入a,las=3,las的fa链为3-1,nxt[3][a]为空,nxt[3][a]=now,跳到1,nxt[1][a]不为空,结束,nxt[1][a]=2,len[1]+1==len[2],fa[4]=2
这里写图片描述
④插入c
这里写图片描述
⑤插入b,nxt[4][b]=now,nxt[1][b]不为空,停止,nxt[1][b]=3,但len[1]+1!=len[3],新开节点7代表3(‘b’),1的fa链nxt[][b]=3的只有1,nxt[1][b]=7,fa[7]=fa[3],fa[now]=fa[3]=7
Step5

复杂度

O(n),不超过2n
proof

int nw(int x){deep[++cnt]=x+1;/*deep就是len*/ return cnt;}
void ins(int las,int c){
    int now=nw(deep[las]);
    for(;las && !tr[las].nxt[c];las=tr[las].pre)tr[las].nxt[c]=now;
    if(!las)tr[now].pre=1;/*pre也就是fa*/ else{
        int u=las,v=tr[u].nxt[c];
        if(deep[u]+1==deep[v])tr[now].pre=v;else{
            int _v=nw(deep[u]);tr[_v]=tr[v];tr[now].pre=tr[v].pre=_v;
            for(;u && tr[u].nxt[c]==v;u=tr[u].pre)tr[u].nxt[c]=_v;
        }
    }
}

性质

  1. 状态x表示的长度范围 (lenprex,lenx]
  2. 一个状态代表的不同字符串在原串中出现次数相等,且每次出现位置末位相同
  3. right(x)right(fax)
  4. 后缀自动机的fa树是原串的反向前缀树
  5. 两个串的最长公共后缀位于所在fa树上的节点的最长公共祖先

求right:
大小:插入新字符时++count[now],最后按照DAG的深度count[fa[x]]+=count[x]

广义后缀自动机

其实就是trie上自动机,每次就在trie_parent[x]所在自动机上的节点后加上x就好了,构造方式完全一样

回到原题

Err?不就是道水题吗
将所有出现的串压到trie中,放入状态,再做广义后缀自动机就好了。

#include<cstring>
#include<cstdio>
#define M 1001001
#define full 1048576
#define max(a,b) (a>b?a:b)
#define lowbit(x) (x&(-x))

using namespace std;

int f[full],cnt,tot,go[M][26],sta[M],n,q,root,p[M],fa[M],que[M],h,t,deep[M+M],v[M+M],b[M+M];
struct SAM{int nxt[26],pre,sta;}tr[M+M];
char s[M];
bool dealt[full];

int nw(int x){deep[++cnt]=x+1;return cnt;}
void ins(int las,int c,int sta,int &to){
    int now=nw(deep[las]);tr[now].sta|=sta;to=now;
    for(;las && !tr[las].nxt[c];las=tr[las].pre)tr[las].nxt[c]=now;
    if(!las)tr[now].pre=1;else{
        int u=las,v=tr[u].nxt[c];
        if(deep[u]+1==deep[v])tr[now].pre=v;else{
            int _v=nw(deep[u]);tr[_v]=tr[v];tr[now].pre=tr[v].pre=_v;
            for(;u && tr[u].nxt[c]==v;u=tr[u].pre)tr[u].nxt[c]=_v;
        }
    }
}

int main(){
    freopen("wechat.in","r",stdin);
    freopen("wechat.out","w",stdout);
    scanf("%d\n",&n);cnt=p[root=0]=1;tr[1].sta=(1<<n)-1;
    for(int i=1;i<=n;i++){
        scanf("%s",s+1);sta[root]+=(1<<i-1);
        for(int t=1,len=strlen(s+1),now=0;t<=len;t++){
            if(s[t]=='<')now=fa[now];else{
                int x=s[t]-97,y;if(!go[now][x])go[now][x]=++tot;
                y=go[now][x];sta[y]|=1<<i-1;fa[y]=now;now=y;
            }
        }
    }
    for(h=0,que[t=1]=0;h!=t;)
        for(int x=que[++h],i=0;i<26;i++)if(go[x][i])ins(p[x],i,sta[que[++t]=go[x][i]],p[go[x][i]]);
    for(int i=1;i<=cnt;i++)b[deep[i]]++;
    for(int i=1;i<=cnt;i++)b[i]+=b[i-1];
    for(int i=1;i<=cnt;i++)v[b[deep[i]]--]=i;
    for(int i=cnt;i;i--)tr[tr[v[i]].pre].sta|=tr[v[i]].sta,f[tr[v[i]].sta]=max(f[tr[v[i]].sta],deep[v[i]]);
    for(dealt[que[t=1]=(1<<n)-1]=1,h=0;h!=t;)for(int x=que[++h],T=x,i=lowbit(T);T;T-=i,i=lowbit(T)){
        f[x-i]=max(f[x-i],f[x]);if(!dealt[x-i])dealt[que[++t]=x-i]=1;
    }
    scanf("%d\n",&q);
    for(int i=1;i<=q;i++){
        int x=0;
        for(int j=1;j<=n;j++)if(getchar()-48)x+=1<<j-1;
        printf("%d\n",f[x]);scanf("\n");
    }
    fclose(stdin);fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值