1.23字符串dp+kmp思路

题目:P1470 [USACO2.3] 最长前缀 Longest Prefix

题目解析

问题描述

给定一个元素集合 ( P ) 和一个大写字母序列 ( s ),要求找到 ( s ) 的最长前缀 ( s' ),使得 ( s' ) 可以由 ( P ) 中的元素组成。元素可以重复使用,但不一定要全部出现。

解题思路

  1. 输入处理
    • 读取元素集合 ( P ),并将其按长度存储在不同的 set 中。
    • 读取大写字母序列 ( s ),并将其拼接成一个连续的字符串。
  2. 动态规划
    • 使用一个动态规划数组 dp,其中 dp[i] 表示前 i 个字符的前缀是否可以由 ( P ) 中的元素组成。
    • 初始化 dp[0] = 1,表示空前缀是合法的。
    • 对于每个位置 i,从 i 向前查找长度不超过 ( m ) 的子串 tt,检查 tt 是否存在于集合 s[tt.size()] 中,并且 dp[i - tt.size()] 是否为 1
    • 如果上述条件满足,则更新 dp[i] = 1,并记录当前前缀的长度 ans
  3. 输出结果
    • 输出 ans,即最长合法前缀的长度。

代码实现

#include <iostream>
#include <set>
#include <cstring>
using namespace std;
int dp[200005], m;
set<string> s[20];
int main() {
    string tp;
    while (cin >> tp) {
        if (tp == ".") break;
        s[tp.size()].insert(tp); // 存到他大小的集合中
        m = max(m, int(tp.size()));
    }
    int i, ans = 0;
    dp[0] = 1; // 初始化
    string n;
    n = " ";
    while (cin >> tp) {
        n = n + tp; // 将所有的串合成一个
    }
    for (i = 1; i < n.size(); i++) { // 枚举子串
        for (int j = min(i, m); j >= 1; j--) {
            string tt = n.substr(i - j + 1, j); // 截除子串
            if (s[tt.size()].count(tt) == 1 && dp[i - j] == 1) { // 如果合法
                ans = i; // 必定是最大的
                dp[i] = 1; // 本身也合法
                break; // 没必要搜下去了
            }
        }
    }
    cout << ans;
    return 0;
}

代码解释

  1. 输入处理
    • while (cin >> tp) 读取元素集合 ( P ),直到遇到 .
    • s[tp.size()].insert(tp) 将每个元素按长度存储在对应的 set 中。
    • m = max(m, int(tp.size())) 记录集合中元素的最大长度。
  2. 动态规划数组初始化
    • dp[0] = 1,表示空前缀是合法的。
  3. 读取并处理大写字母序列 ( s )
    • n = " " 初始化一个空字符串,用于拼接输入的多行字符串。
    • while (cin >> tp) 读取多行字符串,并将其拼接成一个连续的字符串 n
  4. 动态规划状态转移
    • for (i = 1; i < n.size(); i++) 从第 1 个字符开始,逐个字符处理。
    • for (int j = min(i, m); j >= 1; j--) 从当前位置 i 向前查找长度不超过 m 的子串 tt
    • string tt = n.substr(i - j + 1, j) 截取子串 tt
    • if (s[tt.size()].count(tt) == 1 && dp[i - j] == 1) 检查 tt 是否存在于集合 s[tt.size()] 中,并且 dp[i - j] 是否为 1
    • ans = i 更新最长合法前缀的长度。
    • dp[i] = 1 标记当前前缀为合法。
    • break 退出内层循环,避免不必要的检查。
  5. 输出结果
    • cout << ans 输出最长合法前缀的长度。

示例解析

为了更好地理解这个算法,我们可以通过图解来分析动态规划的过程。假设我们有以下输入:
输入:

A AB BA CA BBC
.
ABABACABAABC

1. 输入处理

首先,我们读取元素集合 P 并将其存储在 set 中,按长度分类:

  • s[1] 存储长度为 1 的元素:{A}
  • s[2] 存储长度为 2 的元素:{AB, BA}
  • s[3] 存储长度为 3 的元素:{CA}
  • s[4] 存储长度为 4 的元素:{BBC}
    最大长度 m 为 4。

2. 初始化动态规划数组

  • dp[0] = 1,表示空前缀是合法的。

3. 读取并处理大写字母序列 s

将输入的多行字符串拼接成一个连续的字符串 n

n = " ABABACABAABC"

4. 动态规划状态转移

我们从第 1 个字符开始,逐个字符处理,尝试找到最长的合法前缀。

位置 1: n[1] = 'A'

  • 检查 n[1:1] 是否在 s[1] 中:A 存在。
  • dp[0] = 1,所以 dp[1] = 1
  • ans = 1

位置 2: n[2] = 'B'

  • 检查 n[2:2] 是否在 s[1] 中:B 不存在。
  • 检查 n[1:2] 是否在 s[2] 中:AB 存在。
  • dp[0] = 1,所以 dp[2] = 1
  • ans = 2

位置 3: n[3] = 'A'

  • 检查 n[3:3] 是否在 s[1] 中:A 存在。
  • dp[2] = 1,所以 dp[3] = 1
  • ans = 3

位置 4: n[4] = 'B'

  • 检查 n[4:4] 是否在 s[1] 中:B 不存在。
  • 检查 n[3:4] 是否在 s[2] 中:BA 存在。
  • dp[2] = 1,所以 dp[4] = 1
  • ans = 4

位置 5: n[5] = 'A'

  • 检查 n[5:5] 是否在 s[1] 中:A 存在。
  • dp[4] = 1,所以 dp[5] = 1
  • ans = 5

位置 6: n[6] = 'C'

  • 检查 n[6:6] 是否在 s[1] 中:C 不存在。
  • 检查 n[5:6] 是否在 s[2] 中:AC 不存在。
  • 检查 n[4:6] 是否在 s[3] 中:CAB 不存在。
  • 检查 n[3:6] 是否在 s[4] 中:BAC 不存在。
  • dp[6] = 0ans 保持为 5。

位置 7: n[7] = 'A'

  • 检查 n[7:7] 是否在 s[1] 中:A 存在。
  • dp[6] = 0,所以 dp[7] = 0
  • 检查 n[6:7] 是否在 s[2] 中:CA 存在。
  • dp[5] = 1,所以 dp[7] = 1
  • ans = 7

位置 8: n[8] = 'B'

  • 检查 n[8:8] 是否在 s[1] 中:B 不存在。
  • 检查 n[7:8] 是否在 s[2] 中:AB 存在。
  • dp[6] = 0,所以 dp[8] = 0
  • 检查 n[6:8] 是否在 s[3] 中:CAB 不存在。
  • 检查 n[5:8] 是否在 s[4] 中:BAC 不存在。
  • dp[8] = 0ans 保持为 7。

位置 9: n[9] = 'A'

  • 检查 n[9:9] 是否在 s[1] 中:A 存在。
  • dp[8] = 0,所以 dp[9] = 0
  • 检查 n[8:9] 是否在 s[2] 中:AB 存在。
  • dp[7] = 1,所以 dp[9] = 1
  • ans = 9

位置 10: n[10] = 'A'

  • 检查 n[10:10] 是否在 s[1] 中:A 存在。
  • dp[9] = 1,所以 dp[10] = 1
  • ans = 10

位置 11: n[11] = 'B'

  • 检查 n[11:11] 是否在 s[1] 中:B 不存在。
  • 检查 n[10:11] 是否在 s[2] 中:AB 存在。
  • dp[9] = 1,所以 dp[11] = 1
  • ans = 11

位置 12: n[12] = 'C'

  • 检查 n[12:12] 是否在 s[1] 中:C 不存在。
  • 检查 n[11:12] 是否在 s[2] 中:BC 不存在。
  • 检查 n[10:12] 是否在 s[3] 中:ABC 不存在。
  • 检查 n[9:12] 是否在 s[4] 中:ABBC 不存在。
  • dp[12] = 0ans 保持为 11。

5. 输出结果

最终,最长的合法前缀长度为 11。

图解

我们可以用一个表格来表示 dp 数组的变化:

i 0 1 2 3 4 5 6 7 8 9 10 11 12
n A B A B A C A B A A B C
dp 1 1 1 1 1 1 0 1 0 1 1 1 0
ans 0 1 2 3 4 5 5 7 7 9 10 11 11
posted @ 2025-01-23 23:14  fufuaifufu  阅读(46)  评论(0)    收藏  举报