动态规划

动态规划

1. 切割回文

题目描述
给出一个字符串S,问对字符串S最少切几刀,使得分成的每一部分都是一个回文串(注意:单一字符是回文串)

状态定义
d p [ i ] dp[i] dp[i]代表去字符串的前i位,最少分成多少段的回文串.

状态转移

d p [ i ] = m i n ( d p [ j ] ) + 1   ∣ s [ j + 1 , i ]   i s   p a l i n d r o m e dp[i] = min(dp[j])+ 1\ | s[j + 1, i]\ is \ palindrome dp[i]=min(dp[j])+1 s[j+1,i] is palindrome

1.根据状态转移,算法的时间复杂度 O ( n 2 ) O(n^2) O(n2)

2.所以,我们需要进行优化

代码实现
#include
#include
#include
#include
#include
#include
using namespace std;

#define MAX_N 500000

vector mark[MAX_N + 5];
int dp[MAX_N + 5];

int expand(string &s, int i, int j) {
while(s[i] == s[j]) {
mark[j + 1].push_back(i + 1);
–i, ++j;
if (i < 0 || j >= s.size()) break;
}
return 1;
}

int main() {
string s;
cin >> s;
for (int i = 0; s[i]; i++) {
expand(s, i, i);
i + 1 < s.size() && expand(s, i, i + 1);
}
for (int i = 1; i <= s.size(); i++) {
dp[i] = i;
for (int j = 0; j < mark[i].size(); j++) {
dp[i] = min(dp[i], dp[mark[i][j] - 1] + 1);
}
}

cout << dp[s.size()] - 1 << endl;
return 0;

}


2.扔鸡蛋

题目描述

定义鸡蛋的硬度为 k,则代表鸡蛋最高从 k 楼扔下来不会碎掉,现在给你 n 个硬度相同的鸡蛋,楼高为 m,问最坏情况下最少测多少次,可以测出鸡蛋的硬度。

状态转移过程

d p [ n ] [ m ] = m i n ( m a x { d p [ n − 1 ] [ k − 1 ] + 1 鸡 蛋 碎 了 d p [ n ] [ m − k ] + 1 鸡 蛋 没 碎 ) dp[n][m] = min(max\left\{\begin{aligned}&dp[n-1][k-1]+1&鸡蛋碎了\\&dp[n][m-k]+1 &鸡蛋没碎\end{aligned})\right. dp[n][m]=min(max{dp[n1][k1]+1dp[n][mk]+1)

通过观察 k 与 d p [ n − 1 ] [ k − 1 ] dp[n-1][k-1] dp[n1][k1] d p [ n ] [ m − k ] dp[n][m-k] dp[n][mk]之间的关系,最优的转移 k 值,一定发生在两个函数的交点处

优化掉 min 以后,总体时间复杂度变成了 O ( n × m ) O(n \times m) O(n×m)

状态转移过程

  1. 原状态定义所需存储空间与 m 相关,m 值域大,所以存不下
  2. 当发现某个自变量与因变量之间存在相关性的时候,两者即可对调
  3. d p [ n ] [ m ] = k dp[n][m]=k dp[n][m]=k 重定义为 d p [ n ] [ k ] = m dp[n][k]=m dp[n][k]=m,代表 n 个鸡蛋扔 k 次,最多测多少层楼
  4. k 的值域小,当 n=2 时, k ≤ 2 m k \le \sqrt{2m} k2m

状态转移方程: d p [ n ] [ k ] = d p [ n − 1 ] [ k − 1 ] + d p [ n ] [ k − 1 ] + 1 dp[n][k] = dp[n-1][k-1]+dp[n][k-1] + 1 dp[n][k]=dp[n1][k1]+dp[n][k1]+1

代码实现

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<vector>
using namespace std;
#define MAX_N 100
#define MAX_K 50000
long long dp[MAX_N + 5][MAX_K + 5];

int solve(int n, int m) {
    if (n == 1) return m;
    for (int i = 1; i <= MAX_K; i++)  dp[1][i] = i;
    for (int i = 2; i <= n; i++) {
        for (int k = 1; k <= MAX_K; k++) {
            dp[i][k] = dp[i - 1][k - 1] + dp[i][k - 1] + 1;
        }
    }
    int k = 1;
    while(dp[n][k] < m) k++;
    return k;
}

int main() {
    int n, m;
    cin >> n >> m;
    cout << solve(n, m) << endl;
 
    return 0;
}

3.最长上升子序列

题目描述
有一个数字序列,求其中最长严格上升子序列的长度

状态定义

d p [ i ] dp[i] dp[i],代表以 i 位做为结尾的最长上升子序列的长度

状态转移
d p [ i ] = m a x ( d p [ j ] ) + 1 ∣ v a l j < v a l i dp[i] = max(dp[j]) + 1 | val_j < val_i dp[i]=max(dp[j])+1valj<vali

优化⽅式

  1. 维护⼀个单调数组 len,len[i] 代表⻓度为 i 的序列,结尾最⼩值
  2. 在转移的时候,在 len 数组中查找第⼀个 的位置,
  3. 更新
  4. 需要明确,len 数组为什么是单调的
  5. 证明过程:假设,更新前是单调的,更新以后,⼀定是单调的
  6. 在 len 数组中查找位置 k,实际上就是⼆分算法搞定
    时间复杂度:
    O ( n l o g l ) O(nlogl) O(nlogl)

代码实现

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<vector>
using namespace std;
#define MAX_N 1000000
int dp[MAX_N + 5];
int len[MAX_N + 5];

int binary_search(int *arr, int n, int x){
    int head = 0, tail = n, mid;
    while(head < tail) {
        mid = (head + tail) >> 1;
        if (arr[mid] < x) head = mid + 1;
        else tail = mid;
    }
    return head;
}


int main() {
    int n, ans = 0, a;
    cin >> n;
    memset(len, 0x3f, sizeof(len));
    len[0] =  0;
    for (int i = 1; i <= n; i++) {
        cin >> a;
        dp[i] = binary_search(len, ans + 1, a);
        len[dp[i]] = a;
        ans = max(dp[i], ans);
    }
    cout << ans << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值