[[HNOI2005]星际贸易]

链接 [HNOI2005]星际贸易

  • 条件很多,其实是可以分开做的,有些条件在第一问,有些条件在第二问的。
  • 第一问没有限制,即最大化收益,(f_{i,j})表示前(i)个地点剩下(j)个东西没卖,背包即可。
  • 第二问基于第一问的选择,把第一问的转移扣出来即可,这里要最小化代价,设(g_{i,j})表示当前在(i)地点,燃料剩余(j)的最小花费,那么有:
  • [g_{i,j}=min(g_{i,j-1}+p_i) ]

  • 这是类似于多重背包的转移,也就是不停的在这里买燃料,因为从小往大更新的,所以省去了买多少的枚举复杂度。
  • [g_{i,j}=min(g_{k,j+2}+F_i) ]

  • 表示从别的地方过来,消耗两个燃料,然后修花费(F_i),注意这里满足(L leq Dis_i-Dis_k)(k)在最近一个必经点之后。
  • 直接转移复杂度(O(n^3)),发现维护前缀最小值,但是距离有限制,可以单调队列优化成(O(n^2))
#include<bits/stdc++.h>
#define R register int
#define ll long long
#define db double 
using namespace std;
const int N=4005;
const int inf=2e9;
int n,m,r,L,ans,pos,yes[N],f[N][N],pr[N][N],id[N];
int tl[N],hd[N],G[N][N],a[N],b[N],l[N],P[N],F[N];
int gi(){
	R x=0,k=1;char c=getchar();
	while(c!='-'&&(c<'0'||c>'9'))c=getchar();
	if(c=='-')k=-1,c=getchar();
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();	
	return x*k;
}
int main(){
	freopen("2.in","r",stdin);
	n=gi(),m=gi(),r=min(2*n,gi()),L=gi();
	for(R i=1;i<=n;++i)
		a[i]=gi(),b[i]=gi(),l[i]=gi(),P[i]=gi(),F[i]=gi();
	for(R i=1;i<n;++i)
		if(l[i+1]-l[i]>L)return puts("Poor Coke!"),0;
	memset(f,-63,sizeof(f)),f[0][m]=0;
	for(R i=1;i<=n;++i){
		for(R j=0;j<=m;++j)f[i][j]=f[i-1][j];
		for(R j=0;j+a[i]<=m;++j)
			f[i][j]=max(f[i][j],f[i-1][j+a[i]]+b[i]);
	}
	for(R j=0;j<=m;++j)if(f[n][j]>ans)ans=f[n][j],pos=j;
	for(R i=n,j=pos;i>=1;--i){
		if(f[i-1][j]==f[i][j])continue;
		yes[i]=1,j+=a[i];
	}
	memset(f,63,sizeof(f)),f[0][r]=0,G[r][tl[r]++]=0;
	for(R i=1;i<=n;++i)
		for(R j=0;j<=r;++j){
			if(j&&P[i])f[i][j]=min(f[i][j],f[i][j-1]+P[i]);
			if(hd[j+2]<tl[j+2])f[i][j]=min(f[i][j],f[G[j+2][hd[j+2]]][j+2]+F[i]);
			if(yes[i])hd[j]=tl[j]=0;
			while(hd[j]<tl[j]&&f[i][j]<f[G[j][tl[j]-1]][j])tl[j]--;
			G[j][tl[j]++]=i;
			while(hd[j]<tl[j]&&l[i+1]-l[G[j][hd[j]]]>L)hd[j]++;
		}
	R res=f[0][0];
	for(R j=0;j<=r;++j)res=min(res,f[n][j]);
	if(res==f[0][0])puts("Poor Coke!");
	else printf("%d %d
",ans,ans-res);
	return 0;
}

原文地址:https://www.cnblogs.com/Tyher/p/9845452.html