ICPC World Final 2019 G First of Her Name AC自动机 树上SA 广义后缀自动机

11 篇文章 0 订阅
4 篇文章 0 订阅
题意

给出一个字符构成的树,每个节点到根经过的字符组成一个字符串。有多个询问,每次询问一个字符串,求询问串是多少个字符串的前缀

题解1 树上SA

在树上求后缀数组,求出每个字符串的字典序的排名
对于询问操作,找出询问串在字符串中的排名的区间范围,就得出答案了
由于是有序的,所以可以用二分查找,前缀等于询问串的最小和最大的排名,差值就是答案

注意二分查找时的边界问题: L a = n + 1 , R a = 0 La=n+1,Ra=0 La=n+1,Ra=0
这样,在 R a − L a + 1 Ra-La+1 RaLa+1 时,可以解决答案为0的情况

树上后缀数组

代码
#include<bits/stdc++.h>
#define ll unsigned long long
#define N 1000010
using namespace std;
const int base=31;
int n,m=30,q,cnt;
int sa[N],sa2[N],rk[N],rk2[N],dep[N],tp[N],c[N],fa[N][21],ch[N],la[N],ls[N];

inline void Rsort(int *rk,int *sa){
    for(int i=0;i<=m;i++)c[i]=0;
    for(int i=1;i<=n;i++)c[rk[tp[i]]]++;
    for(int i=1;i<=m;i++)c[i]+=c[i-1];
    for(int i=n;i>=1;i--)sa[c[rk[tp[i]]]--]=tp[i];
}
inline void build_SA(){
    for(int i=1;i<=n;i++)rk[i]=ch[i],tp[i]=i;
    Rsort(rk,sa);
    for(int k=1,w=0;k<n;k<<=1,w++){
        int j=0;
        for(int i=1;i<=n;i++)rk2[i]=rk[fa[i][w]];
        for(int i=1;i<=n;i++)tp[i]=i;
        Rsort(rk2,sa2);
        for(int i=1;i<=n;i++)tp[i]=sa2[i];
        Rsort(rk,sa);swap(rk,tp);rk[sa[1]]=j=1;
        for(int i=2;i<=n;i++)
            rk[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[fa[sa[i]][w]]==tp[fa[sa[i-1]][w]]?j:++j);
        m=j;
    }
    for(int i=1;i<=n;i++)rk[sa[i]]=i;
}
inline int cmp(int x){
    int tot=min(cnt,dep[sa[x]]),k=sa[x];
    for(int i=1;i<=tot;i++){
        if (ch[k]<ls[i]) return -1;
        if (ch[k]>ls[i]) return 1;
        k=fa[k][0];
    }
    if (tot==cnt) return 0;
    return -1;
}

void solve(int len){
    int La=n+1,Ra=0,l=1,r=n;
    while(l<=r){
        int t=l+r>>1;
        if (cmp(t)>=0) La=t,r=t-1;else
            l=t+1;
    }    
    l=1,r=n;
    while(l<=r){
        int t=l+r>>1;
        if (cmp(t)<=0) Ra=t,l=t+1;else
           r=t-1;
    }
    printf("%d\n",Ra-La+1);
}

int main(){
    scanf("%d%d",&n,&q);
    getchar();  dep[1]=1;
    for(int i=1,x;i<=n;i++){
        char c=getchar(); scanf("%d",&x);getchar();
        ch[i]=c-'A'+2;
        fa[i][0]=x;dep[i]=dep[x]+1;
        for(int j=1;j<21;j++)fa[i][j]=fa[fa[i][j-1]][j-1];
    }
    build_SA();
    while(q--){
        char c=getchar();
        cnt=0;
        while(c!='\n') ls[++cnt]=c-'A'+2,c=getchar();
        solve(cnt);
    }
    return 0;
}
题解2 AC自动机

将询问串反向插入自动机中,然后按照字典树的dfs序列在自动机上匹配
注意,匹配成功时,那么 f a i l fail fail 树的子树都匹配成功,统计时,要根据拓扑序求答案
方法可以参考 洛谷P5357的题解

代码
#include<bits/stdc++.h>
#define N 1000010
#define INF 0x3f3f3f3f
#define eps 1e-6
#define pi acos(-1.0)
#define mod 998244353
#define P 1000000007
#define LL long long
#define pb push_back
#define fi first
#define se second
#define cl clear
#define si size
#define lb lower_bound
#define ub upper_bound
#define bug(x) cerr<<#x<<"      :   "<<x<<endl
#define mem(x) memset(x,0,sizeof x)
#define sc(x) scanf("%d",&x)
#define scc(x,y) scanf("%d%d",&x,&y)  
#define sccc(x,y,z) scanf("%d%d%d",&x,&y,&z)
using namespace std;
int w[N],in[N],id[N],n,q,ch[N],ls[N];
int la[N],Q[N];
struct ege{int u,nxt; }G[N]; int cnt;
inline void add(int u,int v){
    G[++cnt]={v,la[u]}; la[u]=cnt;
}

struct Trie{
    int t[N][26],fail[N];
    int tot;
    void ins(int g){
        int i=0;
        for(int j=cnt;j>=1;j--){
            int c=ls[j];
            if(!t[i][c]) t[i][c]=++tot;
            i=t[i][c];
        }
        id[g]=i;
    }
    void get_fail(){
        int h=0,tail=0;
        for (int i=0;i<26;i++) if (t[0][i]) Q[++tail]=t[0][i];
        while(h<tail) {
            int i=Q[++h];
            for(int j=0;j<26;j++) {
                if (t[i][j]){
                    fail[t[i][j]]=t[fail[i]][j];
                    Q[++tail]=t[i][j];
                }else
                t[i][j]=t[fail[i]][j];
            } 
        }
    }
    void AC_automation(int x,int pre){
        for(int i=la[x];i;i=G[i].nxt){
            w[t[pre][ch[G[i].u]]]++;
            AC_automation(G[i].u,t[pre][ch[G[i].u]]);
        }
    }
    void work(){
        for(int i=1;i<=tot;i++) in[fail[i]]++;
        int h=0,tail=0;
        for(int i=1;i<=tot;i++) if(!in[i]) Q[++tail]=i;
        while(h<tail){
            int x=Q[++h];
            int i=fail[x];
            w[i]+=w[x];
            in[i]--;
            if (!in[i]) Q[++tail]=i;
        }
    }
}T1;

char t[N],s[N];

int main(){
    scanf("%d%d",&n,&q); getchar();
    for(int i=1,x;i<=n;i++){
        char c=getchar(); scanf("%d",&x);getchar();
        ch[i]=c-'A';
        add(x,i);
    }
    for(int i=1;i<=q;i++){
        char c=getchar();
        cnt=0;
        while(c!='\n') ls[++cnt]=c-'A',c=getchar();
        T1.ins(i);
    }
    T1.get_fail();
    T1.AC_automation(0,0);
    T1.work();
    for(int i=1;i<=q;i++) printf("%d\n",w[id[i]]);
}
题解3 广义后缀自动机

BFS 建立广义后缀自动机,求出每个状态的 s i z e size size,拿提问串倒序在自动机上跑,结束时到达的状态的 s i z e size size就是答案

代码
#pragma GCC optimize(3)//手动Ox优化
#include<bits/stdc++.h>
#define N 2000010
#define inf 0x3f3f3f3f
#define eps 1e-5
#define pi 3.141592653589793
#define mod 998244353
#define P 1000000007
#define LL long long
#define pb push_back
#define fi first
#define se second
#define cl clear
#define si size
#define lb lower_bound
#define ub upper_bound
#define bug(x) cerr<<#x<<"      :   "<<x<<endl
#define mem(x,y) memset(x,0,sizeof(int)*(y+3))
#define sc(x) scanf("%d",&x)
#define scc(x,y) scanf("%d%d",&x,&y)
#define sccc(x,y,z) scanf("%d%d%d",&x,&y,&z)
using namespace std;

int n, k, fa;
char s[N];
string q;
vector<int> trie[N];

struct SAM {
    int ch[N][26],fa[N],len[N],last[N],a[N],t[N],size[N],cnt=1,root=1;
    int add(int p, int c) {
        if(ch[p][c]&&len[ch[p][c]]==len[p]+1) return ch[p][c];
        int np = ++cnt,fg=0;
        size[np] = 1; len[np] = len[p] + 1;
        while (!ch[p][c] && p) ch[p][c]=np,p=fa[p];
        if (!p) {
            fa[np] = root;
        }else{
            int q = ch[p][c];
            if (len[p]+1==len[q]) {
                fa[np]=q;
            }else{
                if (len[p]+1==len[np]) fg=1;
                int nq = ++cnt;
                memcpy(ch[nq], ch[q], sizeof(ch[q]));
                len[nq]=len[p]+1;
                fa[nq]=fa[q];
                fa[q]=fa[np]=nq;
                while (p&&ch[p][c]==q) ch[p][c]=nq,p=fa[p];
                return fg?nq:np;
            }
        }
        return np;
    }
    void init() {
        //如多次建立自动机,加入memset操作
        root = cnt = 1;
    }
    void build() {
        init();
        queue<int> q;
        q.push(0), last[0] = root;
        while (!q.empty()) {
            int p=q.front(); q.pop();
            for (int i=0;i<trie[p].size();i++) {
                last[trie[p][i]] = add(last[p], s[trie[p][i]] - 'A');
                q.push(trie[p][i]);
            }
        }
        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>root;i--) size[fa[a[i]]] += size[a[i]];
    }
    void solve(){
        int p=1;
        for(int i=q.length()-1;i>=0;i--){
            int c=q[i]-'A';
            if (!ch[p][c]){
                p=0; break;
            }
            p=ch[p][c];
        }
        cout<<size[p]<<'\n';
    }
}SAM;

int main() {
    ios::sync_with_stdio(0);
    cin >> n >> k;
    for (int i = 1; i <= n; ++i) {
        cin >> s[i] >> fa;
        trie[fa].push_back(i);
    }

    SAM.build();
    while (k--) {
        cin >> q;
        SAM.solve();
    }
    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值