因为我还是个菜菜,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;
}
一起加油一起进步呐