bzoj 3879: SvT

题意

有一个长度为n的仅包含小写字母的字符串S,下标范围为[1,n].
现在有若干组询问,对于每一个询问,我们给出若干个后缀(以其在S中出现的起始位置来表示),求这些后缀两两之间的LCP(LongestCommonPrefix)的长度之和.一对后缀之间的LCP长度仅统计一遍.
有S<=5*10^5,且Σt<=3*10^6.

分析

我们把S反过来后建后缀自动机,就转换成了求两两前缀的lcs。显然两个前缀的lcs等于其对应位置在parents树上lca的mx。
那么我们可以把这几个节点的虚树建出来,然后树形dp一下即可

Code

#include <bits/stdc++.h>

typedef long long ll;
const int N = 1000005;

int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}

int ch[N][26];
int num[N],val[N],fa[N];

int sz,last;

void ins(int id,int x)
{
    int p,q,np,nq;
    p = last;
    num[id] = last = np = ++sz;
    val[np] = val[p] + 1;
    for (; p && !ch[p][x]; p = fa[p]) 
        ch[p][x] = np;
    if (!p)
        fa[np] = 1;
    else
    {
        q = ch[p][x];
        if (val[q] == val[p] + 1)
            fa[np] = q;
        else
        {
            nq = ++sz;
            val[nq] = val[p] + 1;
            memcpy(ch[nq], ch[q], sizeof(ch[q]));
            fa[nq] = fa[q];
            fa[q] = fa[np] = nq;
            for (; ch[p][x] == q; p = fa[p])
                ch[p][x] = nq;
        }
    }
}

struct Edge
{
    int to,next;
}e[N * 5];

int next1[N], next2[N];
int cnt;

void add1(int x,int y)
{
    e[++cnt].to = y, e[cnt].next = next1[x], next1[x] = cnt;
}

void add2(int x,int y)
{
    e[++cnt].to = y, e[cnt].next = next2[x], next2[x] = cnt;
}

int bin[25];

int dfn[N],arr[N],dep[N];
int rmq[N * 2][25],lg[25];
int timDfn,tim;

bool cmp(int x,int y)
{
    return dfn[x] < dfn[y];
}

void getRmq()
{
    for (int i = 1; i <= tim; i++) 
        lg[i] = log(i) / log(2);
    for (int j = 1; j <= lg[tim]; j++)
        for (int i = 1; i + bin[j] - 1 <= tim; i++)
            rmq[i][j] = dep[rmq[i][j - 1]] < dep[rmq[i + bin[j - 1]][j - 1]] ? rmq[i][j - 1] : rmq[i + bin[j - 1]][j - 1];
}


void dfs(int x)
{
    dfn[x] = ++timDfn;
    dep[x] = dep[fa[x]] + 1;
    rmq[++tim][0] = x;
    arr[x] = tim;
    for (int i = next1[x]; i; i = e[i].next)
    {
        if (e[i].to == fa[x])
            continue;
        dfs(e[i].to);
        rmq[++tim][0] = x;
    }
}

int getLca(int x,int y)
{
    int l = std::min(arr[x], arr[y]), r = std::max(arr[x], arr[y]);
    int len = lg[r - l + 1];
    return dep[rmq[l][len]] < dep[rmq[r - bin[len] + 1][len]] ? rmq[l][len] : rmq[r - bin[len] + 1][len];
}

ll ans;
int size[N];

void dp(int x)
{
    for (int i = next2[x]; i; i = e[i].next)
    {
        dp(e[i].to);
        ans += 1ll * val[x] * size[x] * size[e[i].to];
        size[x] += size[e[i].to];
        size[e[i].to] = 0;
        next2[e[i].to] = 0;
    }
}

int stack[N];
int tot;

int a[N];

ll solve()
{
    int top = 1, tmp = cnt;
    stack[top] = 1;
    for (int i = 1; i <= tot; i++)
    {
        int x = a[i], lca = getLca(x, stack[top]);
        size[x] = 1;
        while (top > 1 && dep[stack[top - 1]] > dep[lca])
            add2(stack[top - 1], stack[top]), top--;
        if (dep[stack[top]] > dep[lca])
            add2(lca, stack[top]), top--;
        if (lca != stack[top])
            stack[++top] = lca;
        stack[++top] = x;
    }
    while (top > 1)
        add2(stack[top - 1], stack[top]), top--;
    ans = 0;
    dp(1);
    size[1] = 0;
    next2[1] = 0;
    cnt = tmp;
    return ans;
}

char str[N];

int main()
{
    bin[0] = 1;
    for (int i = 1; i <= 20; i++) 
        bin[i] = bin[i - 1] * 2;
    int n = read();
    int m = read();
    scanf("%s", str + 1);
    for (int i = 1; i * 2 <= n; i++) 
        std::swap(str[i], str[n - i + 1]);
    last = sz = 1;
    for (int i = 1; i <= n; i++) 
        ins(i, str[i] - 'a');
    for (int i = 2; i <= sz; i++) 
        add1(fa[i],i);
    dfs(1);
    getRmq();
    while (m--)
    {
        tot = read();
        for (int i = 1; i <= tot; i++) 
            a[i] = num[n - read() + 1];
        std::sort(a + 1, a + tot + 1, cmp);
        tot = std::unique(a + 1, a + tot + 1) - a - 1;
        printf("%lld\n",solve());
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值