2024.02.03动态规划基础之暴力DP

课堂内容

了解动态规划(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=Σfi1,jk​ 其中 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

  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值