[蓝桥杯 2023 省 A] 更小的数(dp基础应用)

本文介绍了如何使用动态规划(DP)解决洛谷P9232题目中的字符串翻转问题,重点在于子串长度上的顺序性递推,通过比较子串内字符决定是否可以翻转。

来源

洛谷P9232

本题dp思想

dp主要思想是:在同一类问题模型下,依赖于已经解决的简单问题基础上,用很小的代价获得更复杂一点的问题的解决方案。
有的题的DP是在下标上顺序性递推的,类似于dp[i]=dp[i-1]+something;
然而这道题的DP是在子串长度上的顺序性递推,而不是在下标上的顺序性递推。

过程

首先算出所有长度为2的子串的dp值,即所有的dp[i][i+1],然后长度依次从3,4,……递增到n,每一个区间从i到j的子串,他们的dp值意味着可否反转这一段的子串,dp=0不可翻转,dp=1可翻转。

对于长度为len的子串,左右下标分别为i和j

d p ( i , j ) = { 1 , if  s t r [ i ] > s t r [ j ] 0 , if  s t r [ i ] < s t r [ j ] d p ( i + 1 , j − 1 ) , if  s t r [ i ] = s t r [ j ] dp_{(i,j)} = \begin{cases} 1, & \text{if } str[i] > str[j] \\ 0, & \text{if } str[i]<str[j] \\ dp_{(i+1,j-1)}, &\text{if } str[i]=str[j] \end{cases} dp(i,j)= 1,0,dp(i+1,j1),if str[i]>str[j]if str[i]<str[j]if str[i]=str[j]

时间复杂度分析

该算法 O ( n 2 ) 预测暴力算法将会是 O ( n 3 ) 该算法O(n^2)预测暴力算法将会是O(n^3) 该算法O(n2)预测暴力算法将会是On3)

代码实现

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

void solve() {
    string str;
    cin >> str;
    int n = str.size();
    
    // 初始化二维数组并默认值为0
    vector<vector<int>> arr(n, vector<int>(n, 0));

    // 长度为2的子串比较
    for (int i = 0; i < n - 1; i++) {
        arr[i][i + 1] = (str[i] > str[i + 1]);
    }

    // 长度 >= 3 的子串处理
    for (int len = 3; len <= n; len++) {
        for (int i = 0; i <= n - len; i++) {
            int j = i + len - 1;
            if (str[i] == str[j])
                arr[i][j] = arr[i + 1][j - 1];
            else
                arr[i][j] = (str[i] > str[j]);
        }
    }

    // 统计满足条件的子串对
    int count = 0;
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            count += arr[i][j];
        }
    }

    cout << count << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    solve();
    return 0;
}

AI生成出来的代码:(同思路,简洁程度更)

#include <iostream>
#include <vector>
#include <string>

using namespace std;

int main() {
    string s;
    cin >> s;
    int n = s.size();
    vector<vector<bool>> dp(n, vector<bool>(n, false));
    
    for (int len = 2; len <= n; ++len) {
        for (int i = 0; i + len - 1 < n; ++i) {
            int j = i + len - 1;
            if (s[i] > s[j]) {
                dp[i][j] = true;
            } else if (s[i] == s[j]) {
                if (j - i >= 2) {
                    dp[i][j] = dp[i+1][j-1];
                } else {
                    dp[i][j] = false;
                }
            }
            // else s[i] < s[j], dp[i][j] remains false
        }
    }
    
    int count = 0;
    for (int i = 0; i < n; ++i) {
        for (int j = i + 1; j < n; ++j) {
            if (dp[i][j]) {
                ++count;
            }
        }
    }
    
    cout << count << endl;
    return 0;
}

### 蓝桥杯 2023次方算法实现方法 #### 扩展欧拉定理的应用 对于计算 $ a^{b} \mod m $ 的问题,当指非常大时(如本题中的 2023 次幂),可以利用 **扩展欧拉定理** 来简化运算。如果满足 $\gcd(a, m) = 1$,则有: $$ a^b \equiv a^{b \mod \phi(m)} \pmod{m} $$ 其中 $\phi(m)$ 是 Euler 函,表示小于等于 $m$ 并且与 $m$ 互质的整[^1]。 因此,在处理蓝桥杯竞赛中涉及的大指幂运算时,可以通过逐步降幂的方式减少复杂度。具体实现如下: ```cpp #include <bits/stdc++.h> using namespace std; // 快速幂函 long long qp(long long base, long long exp, long long mod) { long long res = 1; while(exp > 0){ if(exp % 2 == 1){ res = (res * base) % mod; } base = (base * base) % mod; exp /= 2; } return res; } int main(){ int MOD = 1e9 + 7; // 定义模 vector<long long> phi_values(2024); // 计算Euler函值 for(int i=1;i<=2023;i++) phi_values[i]=i; for(int p=2;p<=2023;p++){ if(phi_values[p]==p){ // 如果是素 for(int j=p;j<=2023;j+=p){ phi_values[j]-=(phi_values[j]/p); } } } long long b = 2023; for(int i=2023;i>=3;i--){ b = qp(i,b,phi_values[i]); } cout << b << endl; return 0; } ``` 上述代码通过快速幂和预处理 Euler 函实现了高效的降幂操作。 --- #### 动态规划解法 另一种可能的方法是从动态规划的角度出发解决问题。假设我们需要统计某个字符串中特定模式(如 `2023`)出现的次,则可以用状态转移的思想来设计解决方案。定义组 $ f[i][j] $ 表示前 $ i $ 位字符匹配到模式串第 $ j $ 位的状态目。例如: - $ f[0] $:前 $ i $ 位中有多少个单独的字 `2` - $ f[1] $:有多少个连续部分形似 `20` - $ f[2] $:有多少个连续部分形似 `202` - $ f[3] $:有多少个完整的 `2023` 序列[^3] 以下是基于此思路的一个伪代码实现: ```python def count_substring(s, pattern="2023"): n = len(s) k = len(pattern) dp = [[0]*k for _ in range(n)] # 初始化第一个字母的情况 for i in range(n): if s[i] == pattern[0]: dp[i][0] += 1 # 状态转移 for j in range(1,k): for i in range(j,n): if s[i] == pattern[j]: dp[i][j] += dp[i-1][j-1] total_count = sum(dp[i][-1] for i in range(n)) return total_count ``` 该方法适用于广泛的场景,比如寻找任意长度固定的子序列在给定据集内的分布情况。 --- #### DFS/回溯算法框架 除了以上两种主要技术路线外,还可以考虑采用深度优先搜索或者回溯策略枚举所有可能性并筛选符合条件的结果。下面给出一个通用模板用于解决排列组合类题目: ```python result = [] def backtrack(path, choices): if is_valid_solution(path): # 判断当前路径是否构成有效解答 result.append(list(path)) # 将合法答案加入最终集合 return # 结束递归分支探索 for choice in choices: # 遍历剩余选项逐一尝试构建新节点 path.append(choice) # 添加至临时存储结构代表做出决定 remaining_choices = update(choices,choice)# 新可用候选项列表 backtrack(path,remaining_choices) # 继续深入下一层级继续试探 path.pop() # 回退至上层重新评估其他候选者 backtrack([], all_possible_elements()) print(result) ``` 这种通用形式能够很好地适应诸如全排列生成器等问题的需求[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值