3277: 串
https://www.lydsy.com/JudgeOnline/problem.php?id=3277
分析:
广义后缀自动机是什么?
广义后缀自动机不就是把很多串的SAM建到了一个SAM上,建每个串的时候都从root开始(last=root)就行了。
广义后缀自动机是Trie树的后缀自动机,可以解决多主串问题。——candy?
首先一个结论:在后缀自动机上:每个状态表示的串的个数为len[i] - len[fa[i]。
这道题我们可以预处理出后缀自动机上每个节点所表示的串 是 哪些主串的子串,记录cnt[i]。如果cnt[i]>=k那么这个节点表示的所有串,会给包含它的主串增加贡献,贡献为Len[i]-len[fa[i]]。
然后枚举每个串的一个前缀,这时只要求出这个前缀有多少个后缀满足>=k就好了。
枚举的每个前缀,它都可以在自动机上走一个点,如果这个点满足>=k,那当前点的所有后缀都可以为它提供贡献,那么就是parent树上其所有祖先和它的和。所以要预先dfs一下每个点的值。
代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long LL; 4 5 inline int read() { 6 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 7 for (;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 8 } 9 10 const int N = 200100; 11 12 struct SuffixAutomaton{ 13 int Last, Index, fa[N], trans[N][26], len[N]; 14 int n, k, cnt[N], cur[N]; 15 bool vis[N]; 16 string s[N]; 17 char ss[N]; 18 19 void extend(int c) { 20 int P = Last, NP = ++Index; 21 len[NP] = len[P] + 1; 22 for (; P&&!trans[P][c]; P=fa[P]) trans[P][c] = NP; 23 if (!P) fa[NP] = 1; 24 else { 25 int Q = trans[P][c]; 26 if (len[P] + 1 == len[Q]) fa[NP] = Q; 27 else { 28 int NQ = ++Index; 29 fa[NQ] = fa[Q]; 30 len[NQ] = len[P] + 1; 31 memcpy(trans[NQ], trans[Q], sizeof trans[Q]); 32 fa[Q] = NQ; 33 fa[NP] = NQ; 34 for (; P&&trans[P][c]==Q; P=fa[P]) trans[P][c] = NQ; 35 } 36 } 37 Last = NP; 38 } 39 void build() { 40 Index = 1; 41 for (int i=1; i<=n; ++i) { // 广义后缀自动机的构建 42 Last = 1; 43 scanf("%s",ss); 44 s[i] = string(ss); 45 for (int j=0,L=s[i].length(); j<L; ++j) 46 extend(s[i][j] - 'a'); 47 } 48 } 49 void dfs(int u) { 50 if (u == 1 || vis[u]) return ; 51 vis[u] = true; 52 dfs(fa[u]); 53 cnt[u] += cnt[fa[u]]; 54 } 55 void solve() { 56 n = read(),k = read(); 57 build(); 58 for (int i=1; i<=n; ++i) { 59 int u = 1; 60 for (int j=0,L=s[i].length(); j<L; ++j) { 61 u = trans[u][s[i][j]-'a']; // 第i个串的一个前缀 62 for (int p=u; p&&cur[p]!=i; p=fa[p]) 63 cur[p] = i, cnt[p] ++;// cnt 当前状态是几个串的子串 64 } 65 } 66 for (int i=1; i<=Index; ++i) cnt[i] = (cnt[i] >= k) * (len[i] - len[fa[i]]); 67 memset(vis, false, sizeof(vis)); 68 for (int i=1; i<=Index; ++i) if (!vis[i]) dfs(i); 69 for (int i=1; i<=n; ++i) { 70 int ans = 0, u = 1; 71 for (int j=0,L=s[i].length(); j<L; ++j) 72 u = trans[u][s[i][j]-'a'], ans += cnt[u]; 73 printf("%d ",ans); 74 } 75 } 76 }sam; 77 78 int main() { 79 sam.solve(); 80 return 0; 81 }
SA的做法:
任何一个子串都是一个后缀的前缀i,考虑枚举每一个后缀j,计算它有多少个前缀是满足的。这里可以二分k。
然后就是判断从有多少个串包含s[i:j],同样的只要看这些串的后缀就行了,由于height数组是单调的,所以再次二分一个mid,表示向前mid个串时候满足之间的height最小值>=k,同样向后也二分一个mid出来,那么就是[L,R]这段区间的任意一个后缀和s[i:]的lcp>=k了,现在求出[L,R]中间有多少不同的串即可,此处可以扫描线预处理出每个i左边第一个满足的包含k个不同串的位置。复杂度$nlog^2n$
fail树的做法?
后缀树的做法?