回文串计数

传送门

题目描述有点复杂,但其实就是要求给定字符串中不相交的回文串个数。

提到不相交的话……我们好像突然想到以前做最长双回文串的时候拼接的这么个思路……所以我们依然可以先用manacher处理出来每个点的最长回文半径,那么在这个点能拓展出来的回文串之内,每一个在中心点前面的点都可以作为一个回文串的开始,与之相对应的,在中心点后面的点可以作为一个回文串的结束。这样的话其实相当于是一次区间修改,我们可以使用差分维护。之后,对于以一个点为末尾的回文串,与之不相交的回文串个数是开头在其之后的所有回文串个数。(我们是从前向后计算的,所以不用计算前面的),这样的话我们计算一下后缀和,每个点分别 乘起来,和就是结果。(当然倒着求前缀和也可以)

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<set>
#include<vector>
#include<queue>
#define pb push_back
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('\n')

using namespace std;
typedef long long ll;
const int M = 40005;
const int N = 2000005;
const ll mod = 51123987;

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

ll p[N<<1],mx,mid,len,ml[N<<1],mr[N<<1],sum,l;
char s[N<<1],c[N];

int change()
{
    l = strlen(c);
    int j = 2;
    s[0] = '!',s[1] = '#';
    rep(i,0,l-1) s[j++] = c[i],s[j++] = '#';
    s[j] = '&';
    return j;
}

void manacher()
{
    len = change(),mx = mid = 1;
    rep(i,1,len-1)
    {
    if(i < mx) p[i] = min(mx-i,p[(mid<<1)-i]);
    else p[i] = 1;
    while(s[i-p[i]] == s[i+p[i]]) p[i]++;
    if(mx < i + p[i]) mid = i,mx = i + p[i];
    mr[(i+1)>>1]++,mr[(i+p[i])>>1]--;
    ml[((i-p[i])>>1)+1]++,ml[(i>>1)+1]--;
    }
    //rep(i,0,l+1) printf("%lld ",mr[i]);enter;
    //rep(i,0,l+1) printf("%lld ",ml[i]);enter;
}

void solve()
{
    rep(i,1,l) ml[i] += ml[i-1],mr[i] += mr[i-1];
    ml[l+1] = 0;
    per(i,l,0) ml[i] += ml[i+1];
    rep(i,1,l) sum += mr[i] * ml[i+1];
}

int main()
{
    scanf("%s",c);
    manacher();
    solve();
    printf("%lld\n",sum);
    return 0;
}

还有一道题与它是一样的,CF17E。传送门

这题要求的是相交的回文串个数,我们只要先求出一共有多少回文串(k),之后我们计算出来回文串对数的总数(k×(k-1)/2)减去所有不重合的即可。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<set>
#include<vector>
#include<queue>
#define pb push_back
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('\n')

using namespace std;
typedef long long ll;
const int M = 40005;
const int N = 2000005;
const ll mod = 51123987;

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

ll n,mx,mid,ml[N<<1],mr[N<<1],sum,cur,len;
ll p[N<<1];
char s[N<<1],c[N];

int change()
{
    int l = strlen(c),j = 2;
    s[0] = '!',s[1] = '#';
    rep(i,0,l-1) s[j++] = c[i],s[j++] = '#';
    s[j] = '$';
    return j;
}

void manacher()
{
    len = change(),mx = mid = 1;
    rep(i,1,len-1)
    {
    if(i < mx) p[i] = min(mx-i,p[(mid<<1)-i]);
    else p[i] = 1;
    while(s[i-p[i]] == s[i+p[i]]) p[i]++;
    if(mx < i + p[i]) mid = i,mx = i + p[i];
    if(!(i&1)) sum++;//a lowercase letter needs add 1.
    sum += (p[i]-1) >> 1,sum %= mod;
    }
    sum = sum * (sum-1) >> 1,sum %= mod;
}

void solve()
{
    for(int i = 2;i <= len-1;i += 2) ml[i-p[i]+2]++,ml[i+2]--,mr[i]++,mr[i+p[i]]--;
    for(int i = 1;i <= len-1;i += 2) ml[i-p[i]+2]++,ml[i+1]--,mr[i+1]++,mr[i+p[i]]--;
    for(int i = 2;i <= len-1;i += 2) ml[i] += ml[i-2],ml[i] %= mod,mr[i] += mr[i-2],mr[i] %= mod;
    ml[len] = 0;
    for(int i = len-2;i >= 1;i -= 2) ml[i] += ml[i+2],ml[i] %= mod;
    for(int i = 2;i <= len-1;i += 2) cur += mr[i] * ml[i+2] % mod,cur %= mod;
    sum -= cur,sum = (sum + mod) % mod;
}

int main()
{
    n = read();
    scanf("%s",c);
    manacher();
    solve();
    printf("%I64d\n",sum);
    return 0;
}

 

转载于:https://www.cnblogs.com/captain1/p/9776075.html

<think>好的,用户问的是如何回文串的个数。首先,我需要明确回文串的定义,就是正读和反读相同的字符串。然后,用户可能是在编程问题中遇到这个问题,比如LeetCode上的题目,或者是算法学习中的一部分。 接下来,我得考虑不同的解决方法。常见的回文串计数方法有两种:暴力枚举和中心扩展法。暴力枚举虽然简单,但时间复杂度高,可能不适用于大数据量的情况。而中心扩展法则更高效,时间复杂度是O(n²),空间复杂度低,适合处理较长的字符串。 然后,用户可能的背景是什么?如果是刚学算法的新手,可能需要详细的步骤解释和代码示例;如果是有经验的开发者,可能更关注优化方法和不同方法的对比。根据用户之前的问题,他们询问过如何在C++中输入字符串,可能对编程有一定基础,但还需要清晰的代码示例和解释。 另外,用户可能想知道如何在不同编程语言中实现,但问题中提到的例子是C++,所以可能需要提供C++的解决方案。同时,要注意处理奇偶长度回文的情况,比如中心扩展法需要分别处理以单个字符为中心和以两个相同字符为中心的情况。 还要考虑特殊情况,比如空字符串、全相同字符的字符串,或者包含非字母字符的情况。是否需要过滤非字母字符?题目是否有具体要?假设题目中的回文串是严格对称的,包括所有字符,不需要预处理。 然后,我需要验证方法的正确性。例如,对于字符串"abc",回文子串个数是3(每个字符自身),而"aaa"则有6个(每个字符自身,以及长度为2和3的回文)。测试用例可以帮助确认算法是否正确。 用户可能还需要了解时间复杂度的分析,为什么中心扩展法更优,以及如何优化暴力法。比如,动态规划方法是否适用,但可能会增加空间复杂度。 最后,总结关键点,提供代码示例,并比较不同方法的优劣,帮助用户根据具体情况选择合适的方法。</think>在编程中回文串个数是一个经典算法问题,以下是两种主流解法及其原理分析: --- ### **方法一:中心扩展法(最优解)** **时间复杂度**:$O(n^2)$ **空间复杂度**:$O(1)$ **核心思想**:以每个字符为中心向两边扩展,统计所有可能的回文子串 ```cpp #include <iostream> using namespace std; int countSubstrings(string s) { int n = s.size(), ans = 0; for (int i = 0; i < n; ++i) { // 奇长度回文(中心为单个字符) for (int l = i, r = i; l >= 0 && r < n && s[l] == s[r]; --l, ++r) ++ans; // 偶长度回文(中心为两个相同字符) for (int l = i, r = i+1; l >= 0 && r < n && s[l] == s[r]; --l, ++r) ++ans; } return ans; } int main() { string s = "abcba"; cout << "回文子串总数:" << countSubstrings(s); // 输出7 return 0; } ``` #### **关键步骤解析** 1. **双重循环结构**: - 外层循环遍历所有可能的中心点($i$从$0$到$n-1$) - 内层循环处理两种扩展情况: - **奇长度扩展**:初始左右指针都指向$i$ - **偶长度扩展**:左指针指向$i$,右指针指向$i+1$ 2. **边界条件**: - 左指针$l \geq 0$ - 右指针$r < n$ - 字符匹配$s[l] == s[r]$ 3. **时间复杂度证明**: - 每个字符最多被访问$2n$次(奇偶各$n$次) - 总时间复杂度为$O(n^2)$ --- ### **方法二:动态规划法** **时间复杂度**:$O(n^2)$ **空间复杂度**:$O(n^2)$ **适用场景**:需要记录所有回文子串位置时 ```cpp int countSubstringsDP(string s) { int n = s.size(), ans = 0; vector<vector<bool>> dp(n, vector<bool>(n, false)); for (int i = n-1; i >= 0; --i) { for (int j = i; j < n; ++j) { // 状态转移方程 if (s[i] == s[j]) { dp[i][j] = (j - i <= 2) || dp[i+1][j-1]; if (dp[i][j]) ++ans; } } } return ans; } ``` #### **动态规划三要素** | 要素 | 说明 | |---------------|-----------------------------| | **状态定义** | $dp[i][j]$表示子串$s[i..j]$是否为回文 | | **状态转移** | $dp[i][j] = (s[i]==s[j]) \land dp[i+1][j-1]$ | | **边界条件** | 长度1或2的子串直接判断首尾字符是否相等 | --- ### **算法对比** | 特性 | 中心扩展法 | 动态规划法 | |----------------|-----------------------|-----------------------| | 时间复杂度 | $O(n^2)$ | $O(n^2)$ | | 空间复杂度 | $O(1)$ | $O(n^2)$ | | 适用场景 | 仅需计数 | 需要记录具体回文位置 | | 实际运行效率 | 更高(常数项小) | 较低(二维数组操作多) | --- ### **典型测试用例** ```cpp void testCases() { cout << countSubstrings("a") << endl; // 1("a") cout << countSubstrings("abc") << endl; // 3(每个字符各1个) cout << countSubstrings("aaa") << endl; // 6(a,a,a, aa,aa,aaa) cout << countSubstrings("abba") << endl; // 6(a,b,b,a, bb, abba) } ``` --- ### **进阶优化技巧** 1. **Manacher算法**(时间复杂度$O(n)$) - 适用于超长字符串($n \geq 10^6$) - 通过插入特殊字符统一处理奇偶长度 - 实现复杂度较高 2. **滚动哈希法** - 预处理字符串的正向和逆向哈希值 - 快速判断子串是否回文 - 时间复杂度$O(n^2)$,但实际运行速度更快 --- **推荐选择策略**: - 面试场景优先使用中心扩展法 - 竞赛场景可考虑Manacher算法 - 需要具体位置信息时使用动态规划法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值