[NOI2018]你的名字 题解

题目链接
先考虑68分的做法:
求在A串中出现,且在B串中没出现的串的数量。
使用容斥,用A的不同子串数减去A,B的不同公共子串数。
先用双指针,求出A的每个位置开始,在B中最多能向后匹配多远。
然后,问题变为,给你一些区间,问它们的子区间中有多少不同的串。
因为每个串,都是原区间\([l,r]\)\([l,i]\)的后缀。\((l<=i<=r)\)
而后缀就是在前面去掉一些字符,就是不断走fa。
所以可以建出fa边的树,然后定位出这些位置,求这些点到根的路径的并。
使用树链合并即可,要注意压缩的问题。
100分做法:与68基本相同,就是判断子串\([a,b]\)是否出现多了一个限制(\(l<=a,b<=r\))。
而一个串匹配的位置,就是这个点子树中红点的集合。
红点对应前缀,所以这个限制可以转化为前缀位置的限制。
需要判断一个点的子树中是否有x~y的数。使用DFS序+主席树即可。

代码:

#include <stdio.h> 
#include <stdlib.h> 
#define ll long long 
int fr[2000010],ne[2000010];
int v[2000010],bs = 0;
int wl[2000010],wr[2000010],tm = 1;
int xl[2000010];
struct SAM {
    int trs[2000010][26],fa[2000010];
    int len[2000010],np,sl;
    int red[2000010];
    SAM() {
        np = sl = 1;
    }
    void clean() {
        for (int i = 1; i <= sl; i++) {
            fa[i] = len[i] = 0;
            red[i] = false;
            for (int j = 0; j < 26; j++) trs[i][j] = 0;
        }
        np = sl = 1;
    }
    void insert(char c, int wz) {
        c -= 'a';
        int p = np;
        np = ++sl;
        len[np] = len[p] + 1;
        while (p != 0 && trs[p][c] == 0) {
            trs[p][c] = np;
            p = fa[p];
        }
        if (p == 0) fa[np] = 1;
        else {
            int q = trs[p][c];
            if (len[q] == len[p] + 1) fa[np] = q;
            else {
                int nq = ++sl;
                fa[nq] = fa[q];
                fa[q] = fa[np] = nq;
                len[nq] = len[p] + 1;
                for (int j = 0; j < 26; j++) trs[nq][j] = trs[q][j];
                while (p != 0 && trs[p][c] == q) {
                    trs[p][c] = nq;
                    p = fa[p];
                }
            }
        }
        red[np] = wz;
    }
};
void addb(int a, int b) {
    v[bs] = b;
    ne[bs] = fr[a];
    fr[a] = bs;
    bs += 1;
}
SAM S,T;
void dfs1(int u) {
    xl[tm] = u;
    wl[u] = tm++;
    for (int i = fr[u]; i != -1; i = ne[i]) dfs1(v[i]);
    wr[u] = tm;
}
int up[1000010];
char zf[1000010];
struct SPx {
    int u,cd;
    SPx() {}
    SPx(int U, int Cd) {
        u = U;
        cd = Cd;
    }
};
SPx px[1000010];
ll baoli(int s) {
    for (int i = 1; i <= T.sl; i++) up[i] = 0;
    for (int i = 0; i < s; i++) {
        int cd = px[i].cd,
        u = px[i].u;
        while (u != 1) {
            if (up[u] == T.len[u] - T.len[T.fa[u]]) break;
            if (cd - T.len[T.fa[u]] > up[u]) up[u] = cd - T.len[T.fa[u]];
            u = T.fa[u];
            cd = T.len[u];
        }
    }
    ll rtn = 0;
    for (int i = 2; i <= T.sl; i++) rtn += up[i];
    return rtn;
}
int he[24000010],cl[24000010],cr[24000010],sl = 0;
int jianshu(int l, int r) {
    int rt = sl++;
    he[rt] = 0;
    if (l + 1 == r) return rt;
    int m = (l + r) >> 1;
    cl[rt] = jianshu(l, m);
    cr[rt] = jianshu(m, r);
    return rt;
}
int xiugai(int i, int l, int r, int j, int x) {
    int rt = sl++;
    if (l + 1 == r) {
        he[rt] = he[i] + x;
        return rt;
    }
    cl[rt] = cl[i];
    cr[rt] = cr[i];
    int m = (l + r) >> 1;
    if (j < m) cl[rt] = xiugai(cl[rt], l, m, j, x);
    else cr[rt] = xiugai(cr[rt], m, r, j, x);
    he[rt] = he[cl[rt]] + he[cr[rt]];
    return rt;
}
int chaxun(int i, int l, int r, int L, int R) {
    if (R <= l || r <= L) return 0;
    if (L <= l && r <= R) return he[i];
    int m = (l + r) >> 1;
    return chaxun(cl[i], l, m, L, R) + chaxun(cr[i], m, r, L, R);
}
int gen[1000010],n;
bool zichuan(int i, int l, int r, int cd) {
    l = l + cd - 1;
    if (l > r) return false;
    int z = chaxun(gen[wr[i] - 1], 1, n + 1, l, r + 1) - chaxun(gen[wl[i] - 1], 1, n + 1, l, r + 1);
    return z > 0;
}
int main() {
    scanf("%s", zf);
    for (n = 0; zf[n] != 0; n++) S.insert(zf[n], n + 1);
    for (int i = 1; i <= S.sl; i++) fr[i] = -1;
    for (int i = 1; i <= S.sl; i++) addb(S.fa[i], i);
    dfs1(1);
    gen[0] = jianshu(1, n + 1);
    for (int i = 1; i < tm; i++) {
        if (S.red[xl[i]] != 0) gen[i] = xiugai(gen[i - 1], 1, n + 1, S.red[xl[i]], 1);
        else gen[i] = gen[i - 1];
    }
    int m;
    scanf("%d", &m);
    for (int i = 0; i < m; i++) {
        int l,r,x = 1,y = 1,s = 0;
        scanf("%s%d%d", zf, &l, &r);
        T.clean();
        for (int j = 0; zf[j] != 0; j++) T.insert(zf[j], 0);
        for (int j = 0, k = -1, c = 0; zf[j] != 0; j++) {
            c -= 1;
            if (x != 1 && k - j == S.len[S.fa[x]]) x = S.fa[x];
            if (y != 1 && k - j == T.len[T.fa[y]]) y = T.fa[y];
            if (k < j) k = j,
            c = 0;
            while (zf[k] != 0 && S.trs[x][zf[k] - 'a'] != 0 && zichuan(S.trs[x][zf[k] - 'a'], l, r, c + 1)) {
                c += 1;
                x = S.trs[x][zf[k] - 'a'];
                y = T.trs[y][zf[k] - 'a'];
                px[s++] = SPx(y, c);
                k += 1;
            }
        }
        ll zo = 0;
        for (int j = 1; j <= T.sl; j++) zo = zo + T.len[j] - T.len[T.fa[j]];
        printf("%lld\n", zo - baoli(s));
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值