课堂内容
了解动态规划(Dynamic Programming, DP)及其解决的问题、根据其设计的算法及优化。
动态规划是一种通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。
由于动态规划并不是某种具体的算法,而是一种解决特定问题的方法,因此它会出现在各式各样的数据结构中,与之相关的题目种类也更为繁杂。
动态规划与其它类型的递推的确有很多相似之处,学习时可以注意它们之间的异同。
最长上升子序列问题(LIS)
纯暴力: O ( 2 n ) O(2^n) O(2n)
暴力dp: f i = m a x { f j + 1 } , j < i , a j < a i f_i=max\{f_j+1\},j<i,a_j<a_i fi=max{fj+1},j<i,aj<ai 时间效率 O ( n 2 ) O(n ^2 ) O(n2)
二分:构造上升目标数组:大数尾部添加,否则局部替换 时间效率 O ( n l o g n ) O(nlogn) O(nlogn)
题目讲解
P1091 [NOIP2004 提高组] 合唱队形
前后各一遍LIS,然后枚举中间最高元素
#include<iostream>
using namespace std;
const int N = 1e2 + 10;
int a[N], lf[N], rf[N], res = 1e9;
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= n; i++) {
lf[i] = 1;
for (int j = 1; j < i; j++) {
if (a[j] < a[i]) {
lf[i] = max(lf[i], lf[j] + 1);
}
}
}
for (int i = n; i >= 1; i--) {
rf[i] = 1;
for (int j = n; j > i; j--) {
if (a[j] < a[i]) {
rf[i] = max(rf[i], rf[j] + 1);
}
}
}
for (int i = 1; i <= n; i++) {
res = min(res, n - (lf[i] + rf[i] - 1));
}
cout << res;
return 0;
}
P1020 [NOIP1999 提高组] 导弹拦截
LIS模型
#include<bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 1e5 + 3;
int n, tot, a[N], f[N];
int main() {
while (~scanf("%d", &a[++n]));
--n;
tot = 0;
memset(f, 0, sizeof(f));
f[0] = inf;
for (int i = 1; i <= n; i++) {
int l = 0, r = tot + 1;
while (r - l > 1) {
int m = l + (r - l) / 2;
if (f[m] >= a[i]) l = m;
else r = m;
}
int x = l + 1;
if (x > tot) tot = x;
f[x] = a[i];
}
printf("%d\n", tot);
tot = 0;
memset(f, 0, sizeof(f));
f[0] = 0;
for (int i = 1; i <= n; i++) {
int l = 0, r = tot + 1;
while (r - l > 1) {
int m = l + (r - l) / 2;
if (f[m] < a[i]) l = m;
else r = m;
}
int x = l + 1;
if (x > tot) tot = x;
f[x] = a[i];
}
printf("%d\n", tot);
return 0;
}
P1077 [NOIP2012 普及组] 摆花
设 f i , j f_{i,j} fi,j 表示到 i i i种花共有 j j j盆时的方案数,不难得到 f i , j = Σ f i − 1 , j − k f_{i,j}=\Sigma f_{i-1,j-k} fi,j=Σfi−1,j−k 其中 k k k为枚举 i i i种花的盆数
#include <bits/stdc++.h>
using namespace std;
const int N = 100 + 5, mod = 1000007;
int n, m, a[N], f[N][N];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
f[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
for (int k = 0; k <= min(j, a[i]); k++) {
f[i][j] = (f[i][j] + f[i - 1][j - k]) % mod;
}
}
}
cout << f[n][m];
return 0;
}
P3842 [TJOI2007] 线段
分四种情况讨论
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int l[N], r[N], n;
ll f[N][2];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d%d", &l[i], &r[i]);
if (i == 1) {
f[1][0] = r[1] + r[1] - l[1] - 1;
f[1][1] = r[1] - 1;
} else {
f[i][0] = min(f[i - 1][0] + abs(l[i - 1] - r[i]) + r[i] - l[i] + 1,
f[i - 1][1] + abs(r[i - 1] - r[i]) + r[i] - l[i] + 1);
f[i][1] = min(f[i - 1][0] + abs(l[i - 1] - l[i]) + r[i] - l[i] + 1,
f[i - 1][1] + abs(r[i - 1] - l[i]) + r[i] - l[i] + 1);
}
}
printf("%lld\n", min(f[n][0] + n - l[n], f[n][1] + n - r[n]));
return 0;
}
作业
https://www.luogu.com.cn/contest/152524