【美团杯2020】半前缀计数

该博客详细介绍了美团杯2020比赛中的一道题目——半前缀计数。文章解释了半前缀的定义,并提出了一种解决方法:通过建立后缀自动机,统计每个前缀后面的不同子串数量,从而得到不同半前缀的总数。题目给出了数据范围1<=|s|<=106,文中提供了相关代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【美团杯2020】半前缀计数

题意:定义半前缀是 s [ 1 : i ] + s [ j : k ] s[1:i]+s[j:k] s[1:i]+s[j:k], 其中 0 ≤ i < l e n ( s ) , i < j ≤ l e n ( s ) , j − 1 ≤ k ≤ l e n ( s ) 0≤i<len(s),i<j≤len(s),j−1≤k≤len(s) 0i<len(s),i<jlen(s),j1klen(s)。直观上来说,你可以把半前缀理解成某一个前缀 s[1:k] 删除掉某一个子串后形成的结果(当然也允许不删)。
给出字符串 s,你需要求出 s 的所有半前缀中,有多少个不同的字符串。
数据范围: 1 < = ∣ s ∣ < = 1 0 6 1<=|s|<=10^6 1<=s<=106
题解:对于一个半前缀 s [ 1 : i ] + s [ j : k ] s[1:i]+s[j:k] s[1:i]+s[j:k],如果有 s [ 1 : i + 1 ] + s [ j + 1 : k ] s[1:i+1]+s[j+1:k] s[1:i+1]+s[j+1:k]与其相等即 s [ i + 1 ] = s [ j ] s[i+1] = s[j] s[i+1]=s[j]时,则说明这个半前缀重复了。因此,对于每个前缀 s [ 1 : i ] s[1:i] s[1:i],我们统计 s [ i + 1 : ∣ s ∣ ] s[i+1:|s|] s[i+1:s]中有多少不同子串不以 s [ i + 1 ] s[i+1] s[i+1]开始,把这些加起来就是答案。我们将串倒序加入后缀自动机,这样我们就能够知道每次以 s [ i ] s[i] s[i]开始的子串数量。然后对于每个前缀统计答案即可。注意计算空串。
代码:

/*
定义半前缀为s[1...i] + s[j...k]
求本质不同的半前缀数量

思路:如果存在一个半前缀s[1...i] s[j...k] 满足 s[i + 1] = s[j] 说明这是重复的半前缀。
*/

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 1e6 + 7;
struct node{
    int pa, son[30], len;
    void init(int l) {
        memset(son, 0, sizeof(son));
        pa = 0;
        len = l;
    }
}sa[maxn << 1];
int cnt, root, last;
int newnode(int len) {
    ++cnt;
    sa[cnt].init(len);
    return cnt;
}
void pre() {
    cnt = 0;
    root = last = newnode(0);
}
int SAM(int alp) {
    int np = newnode(sa[last].len + 1);
    int u = last;
    while(u && !sa[u].son[alp]) sa[u].son[alp] = np, u = sa[u].pa;
    if(!u) sa[np].pa = root;
    else {
        int v = sa[u].son[alp];
        if(sa[v].len == sa[u].len + 1) sa[np].pa = v;
        else {
            int nv = newnode(sa[u].len + 1);
            memcpy(sa[nv].son, sa[v].son, sizeof(sa[v].son));
            sa[nv].pa = sa[v].pa; sa[v].pa = sa[np].pa = nv;
            while(u && sa[u].son[alp] == v) sa[u].son[alp] = nv, u = sa[u].pa;
        }
    }
    last = np;
    return sa[np].len - sa[sa[np].pa].len;
}
char s[maxn];
ll occ[30];
int main() {
    scanf("%s", s + 1);
    ll ans = 0, sum = 0;
    int n = strlen(s + 1);
    pre();
    for(int i = n; i >= 1; --i) {
        int res = SAM(s[i] - 'a');
        ans += sum - occ[s[i] - 'a'] + 1;//加上空串
        sum += res;
        occ[s[i] - 'a'] += res;
    }
    printf("%lld\n", ans + 1);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值