难忘DP啊啊啊

因为我还是个菜菜,dp菜菜too,所以决定从今天开始在这里记录和整理我遇到的一些难忘dp题

装箱问题

P1049 [NOIP2001 普及组]
装箱问题题目链接

大意:
有一个箱子容量为V,同时有n个物品,每个物品有一个体积(正整数)。
要求n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。

思路:

转化成01背包问题(选或不选)。
二维表示:
f[i][j]:箱子容量为j时,前i个物品中选择的最大体积
选了第i个物品:f[i - 1][j - a[i]] + a[i];
没选第i个物品:f[i - 1][j];
一维:
选了第i个物品:f[j - a[i]] + a[i];
没选第i个物品:f[j];

代码

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 33, M = 2e4 + 10;
int a[N], f[M];

int main(){
    int v, n; cin >> v >> n;
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    
    for (int j = 1; j <= n; j ++ ){
        for (int i = v; i >= a[j]; i -- ){
            f[i] = max(f[i - a[j]] + a[j], f[i]);
        }
    }
    cout << v - f[v] << endl;
    return 0;
}

整数分组

整数分组题目链接

大意:
给定 n 个整数 a1,a2,…,an。
现在,请你从中挑选一些数,并将选出的数进行分组。
要求:
选出的数最多划分为 k 组(至少 1 组)。
同一组内,任意两数之差的绝对值不超过 5。
所选出的数尽可能多。
请问,最多可以选出多少个数进行分组?

思路

集合f[i][j]:前i个元素中,分了j组的方案数的最大值。
不用初始化
集合划分:
i个元素包含在分组中,那么就可以把以第i个元素为右边界的组通过双指针找到左边界(可能会考虑到如果这一组的左边界,与前面的最优组有重叠部分,不用担心:重叠部分分给当前组即可,前面不会重复计算),即f[l - 1][j] + (i - l + 1);
第i个元素不包含在分组内,即f[i - 1][j];(下标从1开始)。
结果:f[n][k](考虑到不一定k组是最优解,但是如果最优解组数是<=k的,那可以一分为二,所以最优解一定包含在k组中)。

代码 (双指针+DP)

//下标从1开始,因为f[i - 1][j]; 不用初始化
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5e3 + 10;
int a[N], f[N][N];

int main(){
    int n, k; cin >> n >> k;
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    sort(a + 1, a + 1 + n);
    //也可就是比较拉
    //   for (int i = 1; i <= n; i ++ ){
    //         for (int j = 1; j <= k; j ++ ){
    //             int l = i - 1;
    //             if(a[i] - a[l] <= 5){
    //                 while (l >= 1 && a[i] - a[l] <= 5) l --;
    //             }
    //             f[i][j] = max(f[l][j - 1] + (i - l), f[i - 1][j]);
    //         }
    //     }
    //     cout << f[n][k];
        
    for (int i = 1, l = 1; i <= n; i ++ ){
        while (l < i && a[i] - a[l] > 5) l ++;
        for (int j = 1; j <= k; j ++ ){
            f[i][j] = max(f[l - 1][j - 1] + (i - l + 1), f[i - 1][j]);
        }
    }
    cout << f[n][k] << endl;
    
    return 0;
}

道路优化cf

道路优化题目连接

大意:道路上有n个标志,第i个标志是限速标志。这一限制意味着下一公里必须在a[i]分钟内通过,并且在你遇到下一公里前是有效的。在道路的起点(即坐标为0的点)有一个路标,它设置了初始速度限制。
0 3 4 8 (d)
5 8 3 6 (a)
在这里,你需要在5分钟内完成前3公里,然后在8分钟内完成1公里,然后在3分钟内完成4公里,最后在6分钟内完成最后2公里。总用时3⋅5+1⋅8+4⋅3+2⋅6=47分钟。
为了优化道路交通,火星公司政府决定移除不超过k个路标。不能移除道路起点处的标志,否则起点处将无限制。通过移除这些标识,政府也想让从Kstolop开车到奥林匹克城所需的时间尽可能短。

思路

集合f[i][j]表示前i段中删除j段之后花费的最少时间。
集合划分:前i段中最后连续删除的段有u段,即前i段中删除了k段(这里得k段不同于前面定义得k段,局部变量,并且删除得k段可能超过最开始的k),j段到i段之间都删除了,共u段,那么小明思想得f[j][k - u] + a[j] * (d[i] - d[j]);

代码

我现在只是能看懂代码,自己写还是想不到啊啊啊啊啊啊啊啊啊啊!!!

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 555;
LL d[N], a[N], f[N][N];
int main()
{
    int n, ed, k; cin >> n >> ed >> k;
    for(int i = 1; i <= n; i ++ ) cin>>d[i];
    d[n + 1] = ed;
    for(int i = 1; i <= n; i ++ ) cin>>a[i];
    memset(f, 0x3f, sizeof f);
    //因为第一段是不能删除的,所以第1段种所有的初始化都是0
    for (int i = 0; i <= k; i ++ ) f[1][i] = 0;
    
    for(int i = 1; i <= n + 1; i ++){ 
        for(int k = 0; k < i; k ++){ //前i段路中,只能删除k段(限制了删除第一段)
            for(int j = 1; j < i; j ++){ // j到i之间有u段,u段都删除
            //细节嗷
            //1 2 3 4 5
            //0 3 4 8 10
            //5 8 3 6
            // i = 3, j = 1, u = 1;k = (0->)1 d[j] - d[i] = 4 * a[j] = 5,删掉了3->4那一段
            // i = 4, j = 1, u = 2
                int u = i - j - 1; //-1 !!!!
                if(k < u) continue; //k段必须>=u段
                //要删除的段小于有的段,就说明不能删不了预期的u段啦,就继续。
                //如果当前j~i之间足够删除k段,那么
                //f[i][k]前1~i段中删除k段能达到的最小值,j~i之间删除了u段;
                //那么就是1~j之间删除了k-u段,其中j~i之间的u段就是把j~i之间能删的段都删掉!
                f[i][k] = min(f[i][k], f[j][k - u] + a[j] * (d[i] - d[j]));                       
            }
        }
    }
    LL ans = 1e18;
    //因为结果是删除0~k段,遍历遍历
    for(int i = 0; i <= k; i ++) ans = min(ans, f[n+1][i]);
    cout << ans << endl;
    
    return 0;
}

一起加油一起进步呐

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值