[BZOJ3238][Ahoi2013]差异
3238: [Ahoi2013]差异
Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 3394 Solved: 1542
[Submit][Status][Discuss]
Description
Input
一行,一个字符串S
Output
一行,一个整数,表示所求值
Sample Input
cacao
Sample Output
54
HINT
2<=N<=500000,S由小写英文字母组成
先把答案加的那部分弄出来,可以$O(1)$计算。。
然后剩下的部分可以通过建反串的后缀自动机,那么$Parent$树就是原串的后缀树。。
然后树形DP即可
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 500000 + 10; struct State { int len, link, tot; int son[26]; }st[maxn * 2]; int cnt, last; void sam_init() { cnt = last = 0; st[0].len = 0; st[0].link = -1; st[0].tot = 0; memset(st[0].son, 0, sizeof(st[0].son)); } void sam_extend(char c) { int cur = ++cnt, idx = c - 'a'; st[cur].len = st[last].len + 1; memset(st[cur].son, 0, sizeof(st[cur].son)); st[cur].tot = 1; int p; for (p = last; p != -1 && !st[p].son[idx]; p = st[p].link) st[p].son[idx] = cur; if (p == -1) st[cur].link = 0; else { int q = st[p].son[idx]; if (st[p].len + 1 == st[q].len) st[cur].link = q; else { int clone = ++cnt; st[clone].len = st[p].len + 1; memcpy(st[clone].son, st[q].son, sizeof(st[q].son)); st[clone].link = st[q].link; st[clone].tot = 0; for (; p != -1 && st[p].son[idx] == q; p = st[p].link) st[p].son[idx] = clone; st[q].link = st[cur].link = clone; } } last = cur; } char S[maxn]; int len; struct Edge { int to, next; Edge() {} Edge(int _t, int _n) : to(_t), next(_n) {} }e[maxn * 2]; int fir[maxn * 2] = { 0 }, ecnt = 0; inline void add(int u, int v) { e[++ecnt] = Edge(v, fir[u]); fir[u] = ecnt; } void build() { for (int i = 1; i <= cnt; i++) add(st[i].link, i); } long long ans; void dfs(int u) { for (int v, i = fir[u]; i; i = e[i].next) { v = e[i].to; dfs(v); ans -= 2LL * st[u].len * st[u].tot * st[v].tot; st[u].tot += st[v].tot; } } int main() { scanf("%s", S); sam_init(); len = strlen(S); for (int i = 0; i < len; i++) sam_extend(S[len - i - 1]); build(); ans = (long long)(len + 1) * len * (len - 1) / 2; dfs(0); printf("%lld\n", ans); return 0; }
时隔两年来更新
前面两项可以$O(1)$计算
主要是计算后面一项
注意到$sa$数组是一个$1$到$n$的全排列且$lcp(T_i,T_j)=lcp(T_j,T_i)$
因此$\sum_{1\le i<j\le n}lcp(T_i,T_j)=\sum_{1\le i<j\le n}lcp(T_{sa[i]},T_{sa[j]})$
而$lcp(T_{sa[i]},T_{sa[j]})=min_{i+1\le k\le j}(height[k])$
因此原式可变形为$\sum_{2\le i\le j\le n}min_{i\le k\le j}(height[k])$
可以考虑计算每个$height[i]$作为最小值时被多少个区间包含
显然可以用单调栈维护
注意一下相等数值之间的影响
时间复杂度$O(nlogn)$
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; const int maxn = 500000 + 10; char s[maxn]; int n, m; int sa[maxn], rank[maxn], height[maxn]; int tax[maxn], tp[maxn]; void qsort(){ for(int i = 1; i <= m; i++) tax[i] = 0; for(int i = 1; i <= n; i++) tax[rank[i]]++; for(int i = 2; i <= m; i++) tax[i] += tax[i - 1]; for(int i = n; i; i--) sa[tax[rank[tp[i]]]--] = tp[i]; } bool cmp(int *a, int l, int r, int w){ return a[l] == a[r] && a[l + w] == a[r + w]; } void suffix_sort(){ m = 128; for(int i = 1; i <= n; i++) rank[i] = s[i]; for(int i = 1; i <= n; i++) tp[i] = i; qsort(); for(int k = 1, p = 0; p < n; m = p, k <<= 1){ p = 0; for(int i = 1; i <= k; i++) tp[++p] = n - k + i; for(int i = 1; i <= n; i++) if(sa[i] > k) tp[++p] = sa[i] - k; qsort(); swap(tp, rank); p = rank[sa[1]] = 1; for(int i = 2; i <= n; i++) rank[sa[i]] = cmp(tp, sa[i - 1], sa[i], k) ? p : ++p; } for(int i = 1, j, k = 0; i <= n; height[rank[i++]] = k) for(k ? k-- : 0, j = sa[rank[i] - 1]; s[i + k] == s[j + k]; k++); } int f[maxn], g[maxn], sta[maxn], top; void calc(){ ll ans = (ll)n * (n - 1) * (n + 1) / 2; f[2] = 1; top = 0; for(int i = 2; i <= n; i++){ while(top && height[sta[top]] > height[i]) top--; if(!top) f[i] = i - 1; else f[i] = i - sta[top]; sta[++top] = i; } g[n] = 1; top = 0; for(int i = n; i >= 2; i--){ while(top && height[sta[top]] >= height[i]) top--; if(!top) g[i] = n - i + 1; else g[i] = sta[top] - i; sta[++top] = i; } for(int i = 2; i <= n; i++) ans -= (ll)2 * f[i] * g[i] * height[i]; printf("%lld\n", ans); } int main(){ scanf("%s", s + 1); n = strlen(s + 1); suffix_sort(); calc(); return 0; }