码迷,mamicode.com
首页 > 其他好文 > 详细

Gym 101840B Breaking the Curse (SAM+二分)

时间:2019-08-15 20:55:18      阅读:100      评论:0      收藏:0      [点我收藏+]

标签:bre   sum   while   namespace   back   div   long   strlen   lse   

Problem:
给定两个字符串s1和s2,q次查询,每次查询s1中的一段区间[L,R]中有多少个子串在s2中出现过

 

SOLUTION:

从头开始考虑,我们想知道字串的数量,那我们知道以s1每一个字符为结尾的最长公共字串后,他的所有的后缀都是一个字串

因此

对s2建sam,用s1跑最长公共子串,得到每个位置i往左最多可以匹配到的位置d[i]。显然d[i]是递减。于是对于每个查询,二分得到哪些位置的d[i]是越过L的,这些位置的贡献可以一起得到。剩下的位置的贡献用前缀和维护即可。

CODE:

#include<bits/stdc++.h>
using namespace std;

#define I inline
#define fi first
#define se second
#define pb push_back
#define ALL(X) (X).begin(), (X).end()
#define CLR(A, X) memset(A, X, sizeof(A))
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;

const int N = 1e5+10, SIGMA = 26;

int d[N];
LL sum[N];

struct SAM {
    int last, sz;
    int ch[N<<1][SIGMA], len[N<<1], f[N<<1];

    void init() {
        for(int i = 1; i <= sz; i++) {
            CLR(ch[i], 0);
        }
        last = sz = 1;
        f[1] = len[1] = 0;
    }

    int cal(int p, int c) {
        int q = ch[p][c];
        if(len[q] == len[p]+1) return q;
        int nq = ++sz; len[nq] = len[p]+1;
        memcpy(ch[nq], ch[q], sizeof(ch[q]));
        f[nq] = f[q]; f[q] = nq;
        while(p && ch[p][c]==q) ch[p][c] = nq, p = f[p];
        return nq;
    }

    void insert(int c) {
        int p = last;
        if(ch[p][c]) last = cal(p, c);
        else {
            int np = last = ++sz; len[np] = len[p]+1;
            while(!ch[p][c] && p) ch[p][c] = np, p = f[p];
            if(!p) f[np] = 1;
            else f[np] = cal(p, c);
        }
    }

    void solve(char *s) {
        int sl = strlen(s+1), u = 1, l = 0;
        for(int i = 1; i <= sl; i++) {
            int c = s[i]-‘a‘;
            while(u!=1 && !ch[u][c]) u = f[u], l = len[u];
            if(ch[u][c]) u = ch[u][c], l++;
            d[i] = i-l+1;
            sum[i] = sum[i-1]+l;
        }
    }
}A;

char s1[N], s2[N];

I void work() {
    A.init();
    scanf("%s%s", s1+1, s2);
    int len = strlen(s2);
    for(int i = 0; i < len; i++) A.insert(s2[i]-‘a‘);
    A.solve(s1);
    int q; scanf("%d", &q);
    static int cas = 0;
    printf("Case %d:\n", ++cas);
    while(q--) {
        int l, r; scanf("%d%d", &l, &r);
        int L = l, R = r;
        while(L < R) {
            int M = (L+R)>>1;
            if(d[M] < l) L = M+1;
            else R = M;
        }
        LL ans = 1LL*(L-l)*(L-l+1)/2+sum[r]-sum[L-1];
        printf("%lld\n", ans);
    }
}

int main() {
    if(fopen("curse.in", "r"))
        freopen("curse.in", "r", stdin);
    int X; scanf("%d", &X);
    while(X--) work();
    return 0;
}

  

 

Gym 101840B Breaking the Curse (SAM+二分)

标签:bre   sum   while   namespace   back   div   long   strlen   lse   

原文地址:https://www.cnblogs.com/zhangbuang/p/11360334.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!