题解 - 神秘字符串(mystery)

题目描述

在一个古老的村庄里, 住着一位名叫小唐的年轻女孩。 她对回文情有
独钟, 喜欢挑战字符串中回文子串的权重。 她手中拿着一串由小写字母组
成的神秘字符串, 她想知道在这个字符串中, 回文子串的最大权重是多少。

在她的冒险中, 她遇到了一位名叫艾德加的老者, 他对字符串中的回
文有着独特的见解。 小唐向他请教, 希望从他那里获得一些指导。

在村庄的广场上, 小唐和艾德加一起坐下, 开始分析这个神秘字符串。
他们发现, 字符串中的每个回文子串都有一个“权重”, 这个值等于该回文
子串在字符串中出现的次数乘以其长度。 小唐着迷于这个发现, 她决心找
出字符串中回文子串的最大权重。

小唐和艾德加一起思考, 尝试各种方法来解决这个问题。 他们在村庄
里的每个角落寻找灵感, 希望能够找到答案。 他们决心不放弃, 直到找到
问题的解决方案。

输入

输入为一个字符串 S, 字符串中只包含小写字母。

输出

输出为一个整数, 表示给定字符串 S 中回文子串的最大权重。

样例

【样例 1 输入】
abacaba
【样例 1 输出】
7
【样例 2 输入】
www
【样例 2 输出】
4
【样例 1 说明】
字符串 S 的子串 S[i, j] 是指任意非空字符串 si, si+1, …sj 的集合, 其中
1 ≤ i ≤ j ≤ |S|。 任何字符串也都是其自身的子串。
回文子串是指从左向右和从右向左读取相同的序列。 例如, 字符串
“level” 中的回文子串包括 l, e, v, eve 等。
在第一个样例中有 7 个回文子串 a、 b、 c、 aba、 aca、 bacab、
abacaba。
 a 在给定的字符串中出现了 4 次, 其权重为 4×1=4。
 b 在给定的字符串中出现了 2 次, 其权重为 2×1=2。
 c 在给定的字符串中出现了 1 次, 其权重为 1×1=1。
 aba 在给定的字符串中出现了 2 次, 其权重为 2×3=6。
 aca 在给定的字符串中出现了 1 次, 其权重为 1×3=3。
 bacab 在给定的字符串中出现了 1 次, 其权重为 1×5=5。
 abacaba 在给定的字符串中出现了 1 次, 其权重为 1×7=7。
因此, 回文子串的最大权重为 7。
【数据范围】
|S| 表示字符串的长度。
对于 40% 的数据 1 ≤ |S| ≤ 100;
对于 80% 的数据 1 ≤ |S| ≤ 1000 ;
对于 100% 的数据 1 ≤ |S| ≤ 5000;

思路分析

由于s的长度最多为5000,所以我们直接暴力枚举所有的子串,

若当前字串为回文串,则用map记录数量。

最终,遍历map,取最大值即可。

注意,在枚举并判断回文串时,不可以用下列代码:

for(int len = 1;len <= s.size();len++)
    for(int i = 0;i + len - 1 < s.size();i++){
        string tmp = s.substr(i,len);
        string tmpp = tmp;
        reverse(tmpp.begin(),tmpp.end());
        if(tmp == tmpp) mp[tmp]++;
    }

上述代码第一维枚举长度,第二位枚举起点,看似是O(n^2)的时间复杂度,但实际并不是!

因为substr函数的复杂度是O(n)的,故上述代码的复杂度实际为O(n^3),会超时

因此,我们可以换一种枚举方式,并通过字符串哈希来判断是否为回文串。

for(int i = 1;i <= n;i++){
    string tmp;
    for(int j = i;j <= n;j++){
        tmp += s[j];
        if(get1(i,j) == get2(n - j + 1,n - i + 1)) mp[tmp]++;
    }
}

对于两个string字符串a和b,a += b的时间复杂度为O(len(b)),而a = a + b的时间复杂度为O(len(a) + len(b)),故上述代码枚举的复杂度也为O(n^2),

而我们通过预处理出正序字符串和逆序字符串的哈希表,通过判断get1(i,j)get2(n - j + 1,n - i + 1)是否相等来判断字符串是否为回文串,时间复杂度可以降为O(1)。

ULL get1(int l, int r){
    return h1[r] - h1[l - 1] * p[r - l + 1];
}

ULL get2(int l, int r){
    return h2[r] - h2[l - 1] * p[r - l + 1];
}
// 预处理部分
h1[0] = h2[0] = 0;
p[0] = 1;

for(int i = 1;i <= n;i++){
    h1[i] = h1[i - 1] * P + s[i];
    h2[i] = h2[i - 1] * P + s[n - i + 1];
    p[i] = p[i - 1] * P;
}

时间复杂度为O(n^2)。

此外,若此题数据进一步加强,可以用高级算法-回文树来解决,复杂度为O(nlogn)。

代码1(暴力枚举)

#include<bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef unsigned long long ULL;

const int N = 5000 + 10,P = 131;

char s[N];
map<string,int> mp;
LL res;
ULL h1[N],h2[N],p[N];

ULL get1(int l, int r){
    return h1[r] - h1[l - 1] * p[r - l + 1];
}

ULL get2(int l, int r){
    return h2[r] - h2[l - 1] * p[r - l + 1];
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);

    cin >> s + 1;

    int n = strlen(s + 1);

    h1[0] = h2[0] = 0;
    p[0] = 1;

    for(int i = 1;i <= n;i++){
        h1[i] = h1[i - 1] * P + s[i];
        h2[i] = h2[i - 1] * P + s[n - i + 1];
        p[i] = p[i - 1] * P;
    }

    for(int i = 1;i <= n;i++){
        string tmp;
        for(int j = i;j <= n;j++){
            tmp += s[j];
            if(get1(i,j) == get2(n - j + 1,n - i + 1)) mp[tmp]++;
        }
    }
    
    for(auto it = mp.begin();it != mp.end();it++){
        LL len = it->first.size();
        res = max(res,len * it->second);
    }

    cout << res;

    return 0;
}

代码2(回文树)

#include<bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 5000 + 10;

char s[N];

int tot,last;
struct Tree{
	int son[100];
	int len;
	int fail;
    int cnt;
}tr[N];

void init(){
	tr[1].len = -1,tr[0].len = 0;
	tr[1].fail = 0,tr[0].fail = 1;
	tot = 1,last = 1; 
}

int getfail(int x,int i){
	while(s[i - tr[x].len - 1] != s[i]) x = tr[x].fail;
	
	return x;
}

void insert(int x,int i){
	int fa = getfail(last,i);
	int j = tr[fa].son[x]; 
	if(!j){ 
		j = ++tot;
		tr[fa].son[x] = j;
		tr[j].len = tr[fa].len + 2; 
		int tmp = getfail(tr[fa].fail,i);
		if(fa != 1) tr[j].fail = tr[tmp].son[x];
	}
    tr[j].cnt++;
	last = j;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);

    cin >> s + 1;
    
    int n = strlen(s + 1);

    init();

    for(int i = 1;i <= n;i++) insert(s[i] - 'A',i);

    for(int i = tot;i >= 2;i--){
        tr[tr[i].fail].cnt += tr[i].cnt;
    }

    LL res = 0;
    for(int i = 2;i <= tot;i++) res = max(res,(LL)tr[i].len * tr[i].cnt);

    cout << res;

    return 0;
}
  • 57
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值