洛谷1594 护卫队_区间dp_题解

护卫队

出自洛谷题库
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;
}

【优化方向】
可以考虑如下方向:

  1. 减少预处理的复杂度;
  2. 可以通过逆序循环来减少一个dp数组的维度;(如下)
  3. 简化决策;

【优化代码】->满分
这段代码是看过网上的文章后自己理解所写,无意抄袭

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);

全文——终

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值