PreSuffix ZOJ - 3995(AC自动机 fail树上跑LCA)

题意:
给你n个字符串。
然后q次询问,每次询问n个字符串的中两个字符串的一个最长后缀,满足这个后缀是n个字符串中一些串的前缀。求这个后缀是多少串的前缀。

思路:
建立字典树后再建立fail树。
字典树的每个节点代表着前缀,fail指针指向的位置代表着这个前缀在字典树上能匹配到的最长前缀,满足这个最长前缀为自己的后缀。

那么每次询问两个点,直接在fail树上找到它们的LCA,那就对应两个点的最长后缀且为其他串前缀的串。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int maxn = 1e6 + 7;

struct Trie {
    
    
    int son[30],val,fail;
}t[maxn];

int n,cnt;
char s[maxn];

int pos[maxn];
int head[maxn],to[maxn],nex[maxn],d[maxn],fa[maxn][30];//fa[i][j]代表i点的第2^j个祖先
int tot;

void add(int x,int y)
{
    
    
    to[++tot] = y;
    nex[tot] = head[x];
    head[x] = tot;
}

void dfs(int x)
{
    
    
    for(int i = head[x];i;i = nex[i])
    {
    
    
        int v = to[i];
        if(!d[v + 1])
        {
    
    
            d[v + 1] = d[x + 1] + 1;
            fa[v + 1][0] = x + 1;
            dfs(v);
        }
    }
}

int lca(int x,int y)
{
    
    
    if(d[x] < d[y])swap(x,y);//保证x是最深的
    for(int i = 20;i >= 0;i--)
    {
    
    
        if(d[fa[x][i]] >= d[y])
            x = fa[x][i];//将x移到和y一样的深度
    }
    
    if(x == y)//x移到y了,那说明x就是xy祖先
        return x;
    for(int i = 20;i >= 0;i--)
    {
    
    
        if(fa[x][i] != fa[y][i])
        {
    
    
            x = fa[x][i];
            y = fa[y][i];//x,y一起上移
        }
    }
    return fa[x][0];//此时x和y已经在同一个节点下了,返回x的父节点即可。
}


void init() {
    
    
    for(int i = 0;i <= cnt;i++) {
    
    
        memset(t[i].son,0,sizeof(t[i].son));
        t[i].val = t[i].fail = 0;
    }
    memset(pos,0,sizeof(pos));
    memset(d,0,sizeof(d));
    memset(fa,0,sizeof(fa));
    memset(head,0,sizeof(head));
    tot = 0;
    cnt = 0;
}

void insert(int id,char *s) {
    
    
    int u = 0,len = strlen(s);
    for(int i = 0;i < len;i++) {
    
    
        int v = s[i] - 'a';
        if(!t[u].son[v]) t[u].son[v] = ++cnt;
        u = t[u].son[v];
        t[u].val++;
    }
    pos[id] = u;
}

void getFail() {
    
    
    queue<int>q;
    q.push(0);t[0].fail = 0;
    while(!q.empty()) {
    
    
        int u = q.front();q.pop();
        for(int i = 0;i < 26;i++) {
    
    
            int v = t[u].son[i];
            int Fail = t[u].fail;
            if(!v) {
    
    
                t[u].son[i] = t[Fail].son[i];continue; //直接让不存在的儿子指向失配位置
            }
            if(Fail == 0 && t[Fail].son[i] == v) t[v].fail = 0;
            else t[v].fail = t[Fail].son[i];
            q.push(v);
        }
    }
}

int query(int x,int y) {
    
    
    x = pos[x] + 1;y = pos[y] + 1;
    int z = lca(x,y) - 1;
    return t[z].val;
}

int main() {
    
    
    int n;
    while(~scanf("%d",&n)) {
    
    
        init();
        for(int i = 1;i <= n;i++) {
    
    
            scanf("%s",s);
            insert(i,s);
        }
        getFail();
        for(int i = 1;i <= cnt;i++) {
    
    
            add(t[i].fail,i);
        }
        
        d[1] = 1;
        fa[1][0] = 0;
        dfs(0);
        for(int i = 1;i <= 20;i++)
        {
    
    
            for(int j = 1;j <= cnt;j++)
            {
    
    
                fa[j][i] = fa[fa[j][i - 1]][i - 1];//j的2^i次方祖先等于j的2^i-1次方祖先的2^i-1次方祖先。
            }
        }
        
        int m;scanf("%d",&m);
        while(m--) {
    
    
            int x,y;scanf("%d%d",&x,&y);
            int ans = query(x,y);
            if(!ans) printf("N\n");
            else printf("%d\n",ans);
        }
    }
    return 0;
}


猜你喜欢

转载自blog.csdn.net/tomjobs/article/details/109063059