划分型动规:
给定长度为N的序列或字符串,要求划分成若干段
—段数不限,或指定K段
—每一段满足一定的性质
给定一个正整数N
问最少可以将N分成几个完全平方数(1,4,9,…)之和
例:
输入:N = 13
输出:2(13 = 4 + 9)
确定状态:
最后一步:关注最优策略中最后一个完全平方数 j ^ 2
最优策略中 n-j^2 也一定被分成最少的几个完全平方数之和
得到子问题
状态:设f[i]表示i最少被分成几个完全平方数之和
f[i] = min(f[i - j ^ 2] + 1)
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int n, d[1000000];
int main() {
scanf("%d", &n);
memset(d, 0x3f, sizeof(d));
d[0] = 0;
for(int i = 1; i <= n; i++) {
for(int j = 1; j * j <= i; j++) {
d[i] = min(d[i - j * j] + 1, d[i]);
}
}
for(int i = 0; i <= n; i++) {
printf("%d ", d[i]);
}
printf("%d", d[n]);
return 0;
}
时间复杂度O(N * N^1.5)
给定一个字符串S[0…N-1]
要求将这个字符串划分成若干段,每一段都是一个回文串
求最少划分几次
例子:
输入:
aab
输出:
1(划分1次->aa, b)
回文串判断
回文串分两种:
长度为奇数
长度为偶数
假设我们现在不是寻找回文串,而是生成回文串
从中间开始,向两边扩展,每次左右两端加上同样的字符
对于n个字符的串,奇数回文串的对称轴有n个,偶数回文串的对称轴有n-1个,共有2n个对称轴,一个回文串长度最多为n/2则2n*n/2 = n^2
记录回文串
从S每一个字符开始向两边扩展
考虑奇数回文串和偶数回文串
用isPalin[i][j]表示S[i…j]是否是回文串
时间复杂度O(N^2)
状态方程:
d[i] = min{d[j] + 1| isPalin[j][i - 1] = True}
d[i]表示有几个回文串
答案是d[n] - 1
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int n, d[10000];
bool is[10000][10000];
char s[10000];
void isPalin() {
memset(is, false, sizeof(is));
//奇数长度
for(int c = 0; c < n; c++) {
int i = c;
int j = c;
while(i >= 0 && j < n && s[i] == s[j]) {
is[i][j] = true;
--i;
++j;
}
}
//偶数长度
for(int c = 0; c < n - 1; c++) {
int i = c;
int j = c + 1;
while(i >= 0 && j < n && s[i] == s[j]) {
is[i][j] = true;
--i;
++j;
}
}
}
int main() {
scanf("%s", s);
n = strlen(s);
isPalin();
d[0] = 0;
for(int i = 1; i <= n; i++) {
d[i] = 0x3f3f3f3f;
for(int j = 0; j < i; j++) {
if(is[j][i - 1]) {
d[i] = min(d[i], d[j] + 1);
}
}
}
// for(int i = 0; i <= n; i++) {
// printf("%d ", d[i]);
// }
printf("%d", d[n] - 1);
return 0;
}
Copy Books
有N本书需要被抄写,第i本书有A[i]页,i = 0,1,…,N-1
有K个抄写员,每个抄写员可以抄写连续的若干本书
每个抄写员的抄写速度都一样:一分钟一页
最少需要多少时间抄写完所有的书
输入:
A=3,2,4
K =2
输出:
5(第一个抄写员抄写第1本书和第2本书,第二个抄写员抄写第3本书)
如果一个抄写员抄写第i本到第j本书,则需要时间A[i]+A[i+1]+…+A[j]
最后完成时间取决于耗时最长的那个抄写员
需要找到一种分段方式,分成不超过K段,使得所有段的数字之和的最大值最小
设d[k][i]为前K个抄写员最少需要多少时间抄完前i本书
状态方程:
d[k][i] = min(j =0,…,i){max{d[k-1][j], a[j]+…+a[i - 1]}}
初始条件:
0个抄写员只能抄0本书
d[0][0] = 0, d[0][1] = d[0][2] = …=d[0][N] = 正无穷
k个抄写员(k>0)需要0时间抄0本书
d[k][0] = 0(k > 0)
如果K>N, 赋值K = N
时间复杂度O(KN^2)
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int d[1000][1000], a[1000], n, K;
void DP() {
d[0][0] = 0;
for(int i = 1; i <= n; i++) {
d[0][i] = 0x4f4f4f4f;
}
for(int k = 1; k <= K; k++) {
d[k][0] = 0;
for(int i = 1; i <= n; i++) {
d[k][i] = 0x4f4f4f4f;
int sum = 0;
for(int j = i; j >= 0; --j) {
d[k][i] = min(d[k][i], max(d[k - 1][j], sum));
if(j > 0) {
sum += a[j - 1];
}
}
}
}
}
int main() {
scanf("%d%d", &n, &K);
for(int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
if(K > n ) K = n;
DP();
printf("%d", d[K][n]);
return 0;
}
小结:
划分型动态规划
要求将一个序列或字符串划分成若干段满足要求的片段
解决方法:最后一步->最后一段
枚举最后一段的起点
如果题目不指定段数,用d[i]表示前i个元素分段后的可行性,最值,方式数:第1、2题
如果题目指定段数,用d[i][j]表示前i个元素分成j段后的可行性,最值,方式数:第3题