BZOJ3277: 串(廣義后綴自動機)


Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 1196  Solved: 478
[Submit][Status][Discuss]

Description

字符串是oi界常考的問題。現在給定你n個字符串,詢問每個字符串有多少子串(不包括空串)是所有n個字符串中
至少k個字符串的子串(注意包括本身)。

Input

第一行兩個整數n,k。
接下來n行每行一個字符串。
n,k,l<=100000

Output

輸出一行n個整數,第i個整數表示第i個字符串的答案。

Sample Input

3 1
abc
a
ab

Sample Output

6 1 3

HINT

Source

 

廣義后綴自動機?就是把一坨字符串建到一個后綴自動機上??

不過好難理解啊qwq。。

對於這題,首先我們要知道幾個定理

1.節點$i$表示的本質不同的字符串可以由$len[i] - len[fa[i]]$得到

2.一個串的子串 等價於 一個串所有前綴的所有后綴

這樣子串就轉換為求一個串的前綴的所有后綴的問題

前綴可以枚舉,問題轉換為求一個字符串的各個后綴在其他字符串中出現了多少次

這樣我們可以把廣義后綴自動機建出來,然后暴力沿着$parent$邊跑,這樣可以枚舉出所有后綴

同時為了不重復枚舉,我們需要記錄下每個后綴是否已經被枚舉過了

 

這樣我們就可以知道一個狀態出現的次數是否$>= K$,接下來我們只要統計出這個狀態出現的次數就行了

根據定理$1$,這個很好統計

 

然后就做完啦

 

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int MAXN = 1e6 + 10;
string s[MAXN];
int N, K;
int fa[MAXN], len[MAXN], ch[MAXN][26], root = 1, last = 1, tot = 1, times[MAXN];
void insert(int x) {
    int now = ++tot, pre = last; last = now; len[now] = len[pre] + 1;
    for(; pre && !ch[pre][x]; pre = fa[pre]) ch[pre][x] = now;
    if(!pre) fa[now] = root;
    else {
        int q = ch[pre][x];
        if(len[q] == len[pre] + 1) fa[now] = q;
        else {
            int nows = ++tot; len[nows] = len[pre] + 1;
            memcpy(ch[nows], ch[q], sizeof(ch[q]));
            fa[nows] = fa[q]; fa[q] = fa[now] = nows;
            for(; pre && ch[pre][x] == q; pre = fa[pre]) ch[pre][x] = nows;
        } 
    }
}
int vis[MAXN], sum[MAXN];
void GetTimes() {//求出每一個狀態在幾個字符串出現過 
    for(int i = 1; i <= N; i++) {
        int now = root;
        for(int j = 0; j < s[i].length(); j++) {
            now = ch[now][s[i][j] - 'a'];//枚舉每一個前綴 
            int t = now;
            while(t && vis[t] != i) vis[t] = i, times[t]++, t = fa[t];//枚舉每一個后綴 
        }
    }
}
void dfs(int x) {
    if(x == root || vis[x]) return ;
    vis[x] = 1; 
    dfs(fa[x]); 
    sum[x] += sum[fa[x]];
}
int main() {
#ifdef WIN32
    freopen("a.in", "r", stdin);
#endif
    ios::sync_with_stdio(0);
    cin >> N >> K;
    for(int i = 1; i <= N; i++) cin >> s[i];
    for(int i = 1; i <= N; i++) {
        last = 1;
        for(int j = 0; j < s[i].length(); j++)
            insert(s[i][j] - 'a');
    }
    
    GetTimes();
    
    for(int i = 1; i <= tot; i++) sum[i] = (times[i] >= K) * (len[i] - len[fa[i]]);//i狀態所表示的子串集合對答案的貢獻
    memset(vis, 0, sizeof(vis));
    for(int i = 1; i <= tot; i++) dfs(i);
    for(int i = 1; i <= N; i++) {
        int ans = 0, now = root;
        for(int j = 0; j < s[i].length(); j++)
            now = ch[now][s[i][j] - 'a'], ans += sum[now];
        //枚舉前綴,算出每一個前綴所包含的后綴對答案啊的貢獻 
        printf("%d ", ans);
    }
    
    return 0;
}

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM