动态规划
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[n−1][k−1]+1dp[n][m−k]+1鸡蛋碎了鸡蛋没碎)
通过观察 k 与 d p [ n − 1 ] [ k − 1 ] dp[n-1][k-1] dp[n−1][k−1]与 d p [ n ] [ m − k ] dp[n][m-k] dp[n][m−k]之间的关系,最优的转移 k 值,一定发生在两个函数的交点处
优化掉 min 以后,总体时间复杂度变成了 O ( n × m ) O(n \times m) O(n×m)
状态转移过程
- 原状态定义所需存储空间与 m 相关,m 值域大,所以存不下
- 当发现某个自变量与因变量之间存在相关性的时候,两者即可对调
- 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 次,最多测多少层楼
- k 的值域小,当 n=2 时, k ≤ 2 m k \le \sqrt{2m} k≤2m
状态转移方程: 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[n−1][k−1]+dp[n][k−1]+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])+1∣valj<vali
优化⽅式
- 维护⼀个单调数组 len,len[i] 代表⻓度为 i 的序列,结尾最⼩值
- 在转移的时候,在 len 数组中查找第⼀个 的位置,
- 更新
- 需要明确,len 数组为什么是单调的
- 证明过程:假设,更新前是单调的,更新以后,⼀定是单调的
- 在 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;
}