Bzoj3879:SvT——后缀数组+RMQ+单调栈
题面
解析
求后缀的LCP显然可以用后缀数组
考虑到任意两个后缀的$LCP$是它们在$sa$数组中两个之间的最小的$hei$, 即$LCP(i, j) = min\left \{ hei[k] \right \}(rk[i] < k \leqslant rk[j], rk[i] < rk[j])$, 所以我们把每一组询问按照$rk$排序,再去重,用RMQ求出此时询问中相邻两个后缀的$LCP$, 记为$height$, 我这里的$height[i]$是指询问中$i$与$i-1$的$LCP$
再考虑每一个后缀对答案的贡献,以它为答案的区间,左端点在上一个$height$比它小的后缀与它之间,右端点在它与下一个$height$比它小的后缀之间,注意一下端点选与不选的细节。这个显然可以用单调栈快速维护,用乘法原理搞一下,再加起来就行
代码:
#include<cstdio> #include<iostream> #include<algorithm> #include<cstring> using namespace std; typedef long long ll; const int maxn = 500004; const ll mod = 23333333333333333; int n, Q; char s[maxn]; int q[maxn], sa[maxn], rk[maxn], hei[maxn], fir[maxn], sec[maxn], c[maxn]; ll ans; void Build_SA() { int m = 128; for(int i = 1; i <= n; ++i) fir[i] = s[i]; for(int i = 0; i <= m; ++i) c[i] = 0; for(int i = 1; i <= n; ++i) c[fir[i]] ++; for(int i = 1; i <= m; ++i) c[i] += c[i-1]; for(int i = n; i; --i) sa[c[fir[i]]--] = i; for(int k = 1; k <= n; k <<= 1) { int t = 0; for(int i = n - k + 1; i <= n; ++i) sec[++t] = i; for(int i = 1; i <= n; ++i) if(sa[i] > k) sec[++t] = sa[i] - k; for(int i = 0; i <= m; ++i) c[i] = 0; for(int i = 1; i <= n; ++i) c[fir[sec[i]]] ++; for(int i = 1; i <= m; ++i) c[i] += c[i-1]; for(int i = n; i; --i) sa[c[fir[sec[i]]]--] = sec[i], sec[i] = 0; for(int i = 1; i<= n; ++i) swap(fir[i], sec[i]); t = 0; fir[sa[1]] = ++t; for(int i = 2; i <= n; ++i) if(sec[sa[i]] != sec[sa[i-1]] || sec[sa[i]+k] != sec[sa[i-1]+k]) fir[sa[i]] = ++t; else fir[sa[i]] = t; if(t >= n) break; m = max(m, t); } for(int i = 1; i <= n; ++i) rk[sa[i]] = i; } void Get_hei() { int h = 0; for(int i = 1; i <= n; ++i) { int t = sa[rk[i]-1]; while(s[i+h] == s[t+h]) h++; hei[rk[i]] = h; h = max(0, h-1); } } int mn[20][maxn], lg[maxn]; void RMQ() { lg[0] = -1; for(int i = 1; i <= n; ++i) lg[i] = lg[i>>1] + 1; for(int i = 1; i <= n; ++i) mn[0][i] = hei[i]; for(int i = 1; i <= 16; ++i) for(int j = 1; j <= n; ++j) if(j + (1<<i) - 1 <= n) mn[i][j] = min(mn[i-1][j], mn[i-1][j+(1<<(i-1))]); } bool cmp(int x, int y) { return rk[x] < rk[y]; } int stak[maxn], top, height[maxn], l[maxn], r[maxn]; int main() { scanf("%d%d", &n, &Q); scanf("%s", s+1); Build_SA(); Get_hei(); RMQ(); while(Q--) { ans = 0; int tot; scanf("%d", &tot); for(int i = 1; i <= tot; ++i) scanf("%d", &q[i]); sort(q + 1, q + tot + 1, cmp); tot = unique(q + 1, q + tot + 1) - q - 1; int h = 0; for(int i = 2; i <= tot; ++i) { int t = q[i-1]; height[i] = min(mn[lg[rk[q[i]] - rk[t]]][rk[t] + 1], mn[lg[rk[q[i]]-rk[t]]][rk[q[i]] - (1<<lg[rk[q[i]] - rk[t]]) + 1]); } for(int i = 1; i <= tot; ++i) { while(height[stak[top]] > height[i] && top) r[stak[top--]] = i - 1; l[i] = max(stak[top], 1); stak[++top] = i; } while(top) r[stak[top--]] = tot; for(int i = 1; i <= tot; ++i) ans = (ans + (1LL * (r[i] - i + 1) * (i - l[i]) * height[i] % mod)) % mod; printf("%lld\n", ans); } return 0; }