Bzoj3879:SvT——后缀数组+RMQ+单调栈

题面

   Bzoj3879

解析

   求后缀的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  阅读(189)  评论(0编辑  收藏  举报