护卫队
出自洛谷题库
https://www.luogu.com.cn/problem/P1594
【问题描述】 不是具体题目
大概就是有n个车要过河,只有一个桥(理论上只能单向通行,其实不用管对面),桥有最大承重,每次你可以把其中的任意一段(length<=n)辆车组成车队,同时通过。要求总时间的最小值。
【题目分析】
一开始我在摸索的时候,感觉这道题目有种多个01背包放在一起的感觉,因此,我开始脑补时间复杂度。。。要枚举有几个01背包,要枚举在哪里划分背包,然后还要多个01背包的O(n*n)的累加。。。
然后。。。正解
首先说,这是一道区间dp,这么说的原因是车辆的顺序给定(不能超车),所以我们只能枚举区间,因此,状态定义为二维,f[l][r],即区间左端,右端。
然后,状态转移方程就是在讨论区间的划分,取最小值。
初始化就是把f[i][i],第i辆车的通过时间直接求出来。
最后。。。注释很详细,建议复制到编译器里查看。
【代码+注释】->70分
#include <iostream>
#include <cmath>
#include <queue> //不用加
#include <cstdio>
#include <vector> //不用加
#include <cstring>
#include <algorithm>
using namespace std;
const int MaxN=1000; //固定常量定义数组上限 (水一下代码行数)
double f[MaxN+5][MaxN+5]; //dp数组
double w[MaxN+5][MaxN+5],t[MaxN+5][MaxN+5]; //存下任意区间的重量和以及通过时间( 预处理 );
struct group{
double weight;
double speed;
} a[MaxN+5];
int main()
{
double wmax,distance;
int n;
cin>>wmax>>distance>>n;
for (int i=1;i<=n;i++) {
cin>>a[i].weight>>a[i].speed;
w[1][i]=w[1][i-1]+a[i].weight; //相当于求前缀和,为后面的预处理作铺垫
t[i][i]=f[i][i]=distance/a[i].speed; //求一下从第i到第i辆车的通过时间,即第i辆车的通过时间 (就单辆车而言,f数组与 t数组意义一样)
}
for (int i=1;i<=n;i++) { //预处理
for (int j=i+1;j<=n;j++) { //预处理不算作真正的区间dp,所以可以乱搞。。。求对就好
w[i][j]=w[1][j]-w[1][i-1]; //前缀和作差
t[i][j]=max(t[i][j-1],distance/a[j].speed); //贪心
f[i][j]=1e18; //本题要求最小值,先赋值最大值
}
}
for (int len=2;len<=n;len++) { //枚举区间长度
for (int l=1;l+len-1<=n;l++) { //枚举左端点,通过 len 求出右端点;
int r=l+len-1;
if (w[l][r]<=wmax) f[l][r]=t[l][r]; //这里意思是 :如果该区间本身就可以直接一起通过,自然不需要再讨论如何划分。直接最优。
else for (int k=l;k<r;k++) { //注意区间左闭右开,不然 k+1>r 无意义
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]); // 讨论划分方法
}
} //值得注意的是,区间dp不能直接枚举左右端点,因为区间dp本质是从小区间推广到大区间,只能先从小长度来 (敲黑板)
}
printf("%.1lf\n",f[1][n]*60);
return 0;
}
【优化方向】
可以考虑如下方向:
- 减少预处理的复杂度;
- 可以通过逆序循环来减少一个dp数组的维度;(如下)
- 简化决策;
【优化代码】->满分
这段代码是看过网上的文章后自己理解所写,无意抄袭
double ans[MaxN+5]; //前i辆车的通过时间最小值
for (int i=1;i<=n;i++) ans[i]=1e18;
for (int i=1;i<=n;i++) {
for (int j=i;j>=1;j--) { //相当于是枚举k
if (w[i-j+1][i]<=wmax){
ans[i]=min(ans[i],ans[i-j]+t[i-j+1][i]);
}
}
}
printf("%.1lf\n",ans[n]*60);
全文——终