「HNOI2005」星际贸易

文章介绍了在一个星际贸易问题中,如何利用两次动态规划(DP)来考虑暗物质的购买策略,同时处理维护费用和销售星球之间的冲突。第一次DP确定销售星球,第二次DP在约束条件下计算最小花费。关键在于理解暗物质使用次数和维护成本的关系,以及如何通过单调队列实现滑动窗口操作。
摘要由CSDN通过智能技术生成

原题

将问题分为两次 dp。

题面有:“只有一种获得最大贸易额的方法”

所以直接说明了贸易额与那些费用无关。

有考虑到无论干啥都要维护,第二次 d p dp dp 直接以暗物质为核心即可

对于这里 R R R n ∗ 2 n*2 n2 m i n min min 的一些细节理解。

我们设计状态,因为观察到了暗物质最多也就使用 n ∗ 2 n*2 n2 个,与 R R R 如此之大的范围无关

第二次 d p dp dp 中, f i , j f_{i,j} fi,j 表示到了第 i i i 颗星球,还有 j j j 暗物质的最小花费

正确性:

  • r > n ∗ 2 r > n*2 r>n2 时,直接不买

  • r ≤ n ∗ 2 r \le n*2 rn2 时,做 d p dp dp,由大于 n ∗ 2 n*2 n2 的位置转移过来是不合法的

对于第二次 dp 中,要进行销售的星球与要买暗物质的星球的维护费用冲突的解决

对于考虑暗物质是建立在确定了那些是要销售的星球的基础上的,所以第二次 d p dp dp 对于这些星球的维护费用不应该考虑。

由于我们用单调队列模拟这个滑动窗口的过程,当跑到被选为要销售的星球时,直接 h e a d [ j ] = t a i l [ j ] = 0 head[j] = tail[j] = 0 head[j]=tail[j]=0

所以这里 d p dp dp 可以理解为在每两颗被选星球中间做选择。

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int N = 2010;
int n,m,r,L0;
int ans1,ans2;
bool mark[N];
int dp[N][N];
struct Star{ int goal,val,dis,price,cost; }a[N];
void DP(){
	memset(dp,-1,sizeof dp);
	dp[0][0] = 0;
	for(int i = 1;i<=n;++i)
		for(int j = 0;j<=m;++j){
			if(dp[i-1][j] >= 0)dp[i][j] = dp[i-1][j];
			if(j >= a[i].goal && dp[i-1][j-a[i].goal] >= 0)
				dp[i][j] = max(dp[i-1][j-a[i].goal] + a[i].val,dp[i][j]);
		}
	int res = 0;
	for(int i = 0;i<=m;++i){
//		print(dp[n][i]);
		if(dp[n][i] > dp[n][res])res = i;
	}
	for(int i = n,j = res;i;--i){
		if(dp[i][j] == dp[i-1][j])continue;
		else mark[i] = 1, j -= a[i].goal;
	}
//	print(res);
	ans1 = dp[n][res];
}
int f[N][N];
int tail[N],q[N][N],head[N];
void solve(){
	memset(f,0x3f,sizeof f);
	f[0][r] = 0;
	q[r][++tail[r]] = 0;
	for(int i = 1;i<=n;++i){
		for(int j = 0;j<=r;++j){
			if(a[i].price && j)f[i][j] = min(f[i][j],f[i][j-1] + a[i].price);
			if(tail[j+2] > head[j+2])f[i][j] = min(f[i][j],f[q[j+2][head[j+2]]][j+2] + a[i].cost);
			if(mark[i])head[j] = tail[j] = 0;
			while(head[j] < tail[j] && f[q[j][tail[j]-1]][j] >= f[i][j])--tail[j];
			q[j][tail[j]++] = i;
			while(head[j] < tail[j] && a[i+1].dis - a[q[j][head[j]]].dis > L0)++head[j];
		}
	}
	int res = 0;
	for(int i = 0;i<=r;++i)if(f[n][i] < f[n][res])res = i;
	if(f[n][res] == inf)puts("Poor Coke!");
	else printf("%d %d",ans1,ans1-f[n][res]);
}
int main(){
	scanf("%d%d%d%d",&n,&m,&r,&L0);
	r = min(r,n<<1);
	a[0] = {0,0,0,0,0};
	for(int i = 1;i<=n;++i)
		scanf("%d%d%d%d%d",&a[i].goal,&a[i].val,&a[i].dis,&a[i].price,&a[i].cost);
	for(int i = 1;i<=n;++i)if(a[i].dis - a[i-1].dis > L0)return puts("Poor Coke!"),0;
	DP();
	solve();
	return 0;
}
  • 33
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值