Codeforces 432D 完美子串 + 51nod 1129 字符串最大值(dp-kmp)

本文深入探讨了KMP算法的应用及其实现细节,特别是在字符串匹配问题中的高效解决方案。通过实例讲解了如何利用KMP算法求解特定字符串问题,并详细解释了next数组的作用与计算方法。

这里写图片描述

我觉得我能A这题完全是因为前一天晚上做了很类似的。

字符串,前缀后缀,出现次数,关键词一拼就是kmp。
首先我们考虑第二问的第二个数,怎么求得一段前缀子串在 s 中出现的次数。这里需要我们深入地了解next数组的含义。

这里写图片描述

呃,我的意思是,如果 s[ 1~i ]出现过x次,那么 s[ 1~next[ i ] ]也必然至少出现过x次。
由此,我们设 d[ i ]表示 s[ 1~i ]出现的次数,可得推导方程:
d[ next[ i ] ]+= d[ i ];

刘汝佳在《入门经典》里讲DAG上的dp的时候好像说过,这种 “‘对于每个状态 i,更新 d[ i ]所影响到的状态 j’的方法称为刷表法。因为这不是一个可以直接计算 d[ j ]的方程,所以只称它为更新公式“。当时不太理解,现在才有点明白了。

注意,这里递推的时候需要倒序,理由十分直观,next[ i ]必然在 i 的前面,倒推能保证 d[ i ]一定被更新完毕。

51nod 1129 字符串最大值 那道题至此已经可以解决了。

好了,那么看第一问和第二问第一个数。
next[ len ] 表示 s[ 1~next[ len ] ] 和 s[ len-next[ len ]+1,len ]是相同的,所以它必然是符合题意的,而且是最长的那一个。那么 next [ next[ len ] ]呢?根据 next 数组的定义,它是 s[ 1~next[ len] ]的前缀,也是它的后缀,也是 s[ len-next[ len ]+1,len ] 的后缀,即,它是 s 的后缀,所以这也是满足题意的。
所以,这一问的正解就是无限跳跳跳,不停地 next[ next[ next[ ….] ] ],cnt++,直到 next 为0。这个过程中,把符合题意的前缀子串长度记录下来,只需要输出这个值 x 以及它对应的 d[ x ] 就是答案啦。

深入理解 next ,它真的好强。

char s[100010];
int lens;
int next[100010];
int d[100010];
int ans;

bool f[100010];//f[i]表示长度为i的前缀子串是否合法。

void kmp_()
{
    memset(next,0,sizeof(next));
    int j=0;
    for(int i=2;i<=lens;++i)
    {
        while(j&&s[i]!=s[j+1]) j=next[j];
        if(s[i]==s[j+1]) ++j;
        next[i]=j;
    }
}

void dfs(int i)
{
    if(!i) return;
    ++ans;f[i]=1;
    dfs(next[i]);
}

void work()
{   
    kmp_();

    memset(d,0,sizeof(d));
    for(int i=1;i<=lens;++i) d[i]=1;
    for(int i=lens;i>=1;--i) d[next[i]]+=d[i];

    ans=0;
    memset(f,0,sizeof(f));
    dfs(lens);
    printf("%d\n",ans);
    for(int i=1;i<=lens;++i) if(f[i]) printf("%d %d\n",i,d[i]);
}

int main()
{
    scanf("%s",s+1);
    lens=strlen(s+1);
    work();
    return 0;
}

Q^Q

### Codeforces 432D 后缀数组 C++98 实现代码 以下是基于问题需编写的针对 Codeforces 432D 的后缀数组实现代码,采用的是 C++98 版本的语言特性。此代码实现了倍增法来构建后缀数组,并解决了题目中的核心逻辑。 #### 倍增法构建后uffix Array 倍增法的核心思想在于逐步增加比较的长,每次将当前已排序的部分扩展一倍,直到覆盖整个字符串[^2]。这种方法的时间复杂为 \(O(n \log n)\),适合用于解决此类问题。 ```cpp #include <cstdio> #include <cstring> const int MAXN = 1e5 + 10; int sa[MAXN], rank_arr[MAXN], tmp_rank[MAXN]; int c[MAXN]; // 计数排序辅助函数 void counting_sort(int n, int k) { memset(c, 0, sizeof(c)); for (int i = 0; i < n; ++i) c[i + k < n ? rank_arr[i + k] : 0]++; for (int i = 1; i < n; ++i) c[i] += c[i - 1]; for (int i = n - 1; i >= 0; --i) sa[--c[i + k < n ? rank_arr[i + k] : 0]] = i; } // 构建后缀数组 void build_suffix_array(const char* s, int n) { for (int i = 0; i < n; ++i) { sa[i] = i; rank_arr[i] = s[i]; } for (int k = 1; k < n; k <<= 1) { counting_sort(n, k); counting_sort(n, 0); tmp_rank[sa[0]] = 0; int r = 0; for (int i = 1; i < n; ++i) { if (rank_arr[sa[i]] != rank_arr[sa[i - 1]] || rank_arr[sa[i] + k < n ? sa[i] + k : 0] != rank_arr[sa[i - 1] + k < n ? sa[i - 1] + k : 0]) ++r; tmp_rank[sa[i]] = r; } memcpy(rank_arr, tmp_rank, sizeof(tmp_rank)); if (rank_arr[sa[n - 1]] == n - 1) break; } } ``` #### 主程序部分 以下为主程序框架,结合输入数据完成具体功能: ```cpp char s[MAXN]; int lcp_arr[MAXN]; // LCP 数组存储长公共前缀 // Kasai 算法计算LCP数组 void kasai_lcp(char* s, int* sa, int* rank_arr, int n, int* lcp_arr) { for (int i = 0; i < n; ++i) rank_arr[sa[i]] = i; int h = 0; lcp_arr[0] = 0; for (int i = 0; i < n; ++i) { if (h > 0) --h; if (rank_arr[i] == n - 1) { lcp_arr[rank_arr[i]] = 0; h = 0; continue; } int j = sa[rank_arr[i] + 1]; while (i + h < n && j + h < n && s[i + h] == s[j + h]) ++h; lcp_arr[rank_arr[i]] = h; } } int main() { scanf("%s", s); int n = strlen(s); build_suffix_array(s, n); kasai_lcp(s, sa, rank_arr, n, lcp_arr); // 输出结果(根据实际题目调整) for (int i = 0; i < n; ++i) { printf("%d ", sa[i]); } putchar('\n'); for (int i = 0; i < n; ++i) { printf("%d ", lcp_arr[i]); } return 0; } ``` 以上代码完成了后缀数组及其对应的 LCP 数组的构建过程。对于具体的 Codeforces 432D 题目细节,可以根据其特殊条件进一步优化或修改逻辑。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值