# 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;
}
View Code

posted @ 2019-08-13 09:47  Mr_Joker  阅读(147)  评论(0编辑  收藏  举报