「HDU 5677」 ztr loves substring - Manacher + 多重背包

本文介绍了解决HDU5677问题的方法,通过使用Manacher算法来计算字符串中的回文子串,并结合动态规划解决多重背包问题,最终判断是否存在特定数量的子串使得其长度之和等于给定值。

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

建议访问原文出处,获得更佳浏览体验。
原文出处:https://hyp1231.github.io/2018/07/10/20180710-hdu5677/

题意

给出 N N 个字符串,所有字符串的连续回文子串构成一个多重集合 S
S S 中是否存在恰好 K 个串,使它们的长度之和为 L L

链接

HDU5677 ztr loves substring

题解

只与每个串的长度有关,故应先预处理出每个长度的回文串的数量。

使用 Manacher 算法计算出以串的各个位置为中心的最大回文串长度。若以 si 为中心的最大回文串的长度为 l l (不妨假设它为偶数),考虑逐步去掉两侧字符就可以造出更多回文子串。因此长度为 2,4,,l 的子串的计数器分别加一(若 l l 为奇数,同理)。这样我们就得到了每个长度的串的数量,

考虑选出 K 个串使它们长度之和为 L L ​ 。即选出 K K ​ 个物品,填满容量为 L L ​ 的背包,每种物品的个数已知,是多重背包问题。设计 dp[i][j] d p [ i ] [ j ] ​ 表示当前已经选了 i i ​ 个串,放在容量为 j j ​ 的背包中的最大容量。记放入新串后数量为 num n u m ​ ,当前背包容量为 p p ​ ,新增串个数为 mul m u l ​ ,新增子串长 i i ​ ,则状态转移方程为:
dp[num][p]=MAX(dp[num][p],dp[nummul][pmuli]+muli) d p [ n u m ] [ p ] = M A X ( d p [ n u m ] [ p ] , d p [ n u m − m u l ] [ p − m u l ⋅ i ] + m u l ⋅ i ) ​

Hint 1: 转移要求在 num=mul n u m = m u l (第一次添加串)或 dp[nummul][pmuli] d p [ n u m − m u l ] [ p − m u l ⋅ i ] 不为 0 0 时才转移(说明存在上一个状态)。
Hint 2: 使用二进制优化加速多重背包。

记长度为 i 的回文子串的数量为 Wi W i 。读入和 N N 次 Manacher 是 O(NL) 的,多重背包是 O((i=1LlogWi)KL) O ( ( ∑ i = 1 L log ⁡ W i ) K L ) 。总时间复杂度 O((i=1LlogWi)KL+NL) O ( ( ∑ i = 1 L log ⁡ W i ) K L + N L )

代码

#include <cstdio>
#include <algorithm>
#include <cstring>

const int N = 128;
const char mid_c = '$';
const char side_c = '@';

int n, k, l;
char s[N][N];
char s2[N << 1];
int len, r[N][N << 1];

void prepare(char s1[]) {
    len = 0;
    s2[len++] = side_c;
    s2[len++] = mid_c;

    int n = strlen(s1);
    for (int i = 0; i < n; ++i) {
        s2[len++] = s1[i];
        s2[len++] = mid_c;
    }
    s2[len + 1] = '\0';
}

void Manacher(char s1[], int id) {
    prepare(s1);
    memset(r[id], 0, sizeof(r[id]));
    int mid = 0, p = 0;
    for (int i = 0; i < len; ++i) {
        int x;
        if (p < i)x = 1;
        else x = std::min(p - i, r[id][2 * mid - i]);
        while (s2[i - x] == s2[i + x]) ++x;
        if (i + x > p) {
            p = i + x;
            mid = i;
        }
        r[id][i] = x;
    }
}

int cnt[N], dp[N][N];
// dp[i][j] 代表 i 个子串拼接成的串的最大长度(不超过 j)

int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        memset(cnt, 0, sizeof(cnt));
        memset(dp, 0, sizeof(dp));
        scanf("%d%d%d", &n, &k, &l);
        for (int i = 0; i < n; ++i) {
            scanf("%s", s[i]);
        }
        for (int i = 0; i < n; ++i) {
            Manacher(s[i], i);
            for (int j = 0; j < len; ++j) {
                int tmp = r[i][j] - 1;
                if (tmp <= 0) continue;
                while (tmp > 0) {           // 最长子串逐步去掉侧边字母也是子串
                    ++cnt[tmp];
                    tmp -= 2;
                }
            }
        }
        for (int i = 0; i <= l; ++i) {      // 枚举子串长度
            int sum = cnt[i];               // 长度为 i 的子串数量
            for (int j = 1; sum; j <<= 1) { // 二进制优化
                int mul = std::min(sum, j);
                for (int p = l; p >= mul * i; --p)      // 枚举长度
                    for (int num = mul; num <= k; ++num)// 枚举已选字符串个数
                        if (num == mul || dp[num - mul][p - mul * i] > 0)
                            // 如果本串是第一次被选,或存在选了 p - mul * i 个串的方案
                            dp[num][p] = std::max(dp[num][p], 
                                                  dp[num - mul][p - mul * i] + mul * i);
                sum -= mul;
            }
        }
        printf("%s\n", (dp[k][l] == l ? "True" : "False"));
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值