题解 P2317 [HNOI2005] 星际贸易

题解 P2317 [HNOI2005] 星际贸易


题意

先化简,然后变成了这个样子:

Coke 依次经过 \(1\) ~ \(n\) 这几个位置,每个位置离起点距离为 \(L_i\) ,最后停靠在第 \(n\) 个位置结束。

在每一个位置可以通过付出 \(A_i\) 的代价获取 \(B_i\)​ 的价值,他总共可以承担 \(m\) 的代价。

在离开和到达某个位置时,都要使用 \(1\) 点能量,在每个位置都有可能可以补充能量,价格是 \(P_i\),一开始,总共有 \(R\) 点能量。

同时在不管哪个位置停靠,都要在这个位置进行维护,费用为 \(F_i\),不维护最多可以走的距离是 \(L_0\)

在满足价值最大的同时,他希望花在能量与维护上的总价最少。

其中,输入数据满足:一定有 ,并使得只有—种获得最大价值的方法


分析

再通过简化的题意发现,最大价值与能量与维护都无关,所以直接使用一个01背包就可以解决:

\(f_{i,j}\) 表示:到第 \(i\) 个位置,花费 \(j\) 的代价,最大总价值为多少。然后就是最基础的01背包。

#define RCL(a,b,c,d) memset((a),(b),sizeof(c)*(d))
int n,m;
int a[N],b[N];
int f[N][N];
RCL(f,-1,f,1),f[0][0]=0;
FOR(i,1,n)FOR(j,0,m){
	f[i][j]=f[i-1][j];
	if(j>=a[i])tomax(f[i][j],f[i-1][j-a[i]]+b[i]);
}

再看题目中有一个十分明显的提示:

输入数据满足:一定有 ,并使得只有—种获得最大价值的方法

那么我们找到最大值,然后反向寻找一下就能知道那几个位置是做过买卖的,也就是必经的位置:

int n,m,id1,id2;
int a[N];
int f[N][N];
bool chosen[N];
FOR(i,1,m)if(f[n][i]>f[n][id1])id1=i;
id2=id1;
DOR(i,n,1)if(f[i][id2]!=f[i-1][id2])id2-=a[i],chosen[i]=1;

然后既然知道哪些点是必经的,我们就可以把能量与维护的费用独立出来进行一个 DP:

\(g_{i,j}\) 表示:到第 \(i\) 个位置,剩余 \(j\) 的能量,最小总价为多少。

那么列出转移方程式:

\[g_{i,j} = \min{\{ g_{k,q} + F_i + P_i*(j-q+2)| L_i-L_k \le L_0 , 2\le q \le j-2 \}} , \]

由此可以解出 \(O(n^4)\) 的算法,但是有一个明显的化简,我们把二维寻找转换为一维:

\[g_{i,j} = \min {( \min{\{ g_{k,j+2} + F_i | L_i-L_k \le L_0 \}} , g_{i,j-1} + P_i )} \]

然后可以得到一个 \(O(n^3)\) 的的做法,不过直接枚举还不够,我们用一种简单的数据结构优化:单调队列。

同时注意到,总共用的能量不会超过 \(2n\) ,那么就可以在输入时就把他化掉。

那么再结合上面的细节即可打出代码,(码风清奇,请见谅):

struct Deque{
#define Pii pair<int,int> 
#define F first
#define S second
	deque< Pii > dq;
	Deque(){Clear();}
	inline void Clear(){while(!dq.empty())dq.pop_back();}
	inline void Push(Pii x){
		while(!dq.empty()&&dq.back().S>=x.S)dq.pop_back();
		dq.emplace_back(x);
	}
	inline void Update(int x){while(!dq.empty()&&dq.front().F<x)dq.pop_front();}
	inline int Query(){return dq.empty()?INF:dq.front().S;}
#undef Pii
#undef F
#undef S
}dq[N<<1];
#define RCL(a,b,c,d) memset((a),(b),sizeof(c)*(d))
int n,R,L;
int l[N],P[N],F[N];
int g[N][N<<1];
tomin(R,(n<<1));
RCL(g,INF,g,1),g[0][R]=0;
dq[R].Push({0,0});
FOR(i,1,n)FOR(j,0,R){
	if(P[i]&&j)tomin(g[i][j],g[i][j-1]+P[i]);
	dq[j+2].Update(l[i]-L),tomin(g[i][j],dq[j+2].Query()+F[i]);
	if(chosen[i])dq[j].Clear();
	dq[j].Push({l[i],g[i][j]});
}

紧接着,找到最小值:

int R,id1,id2;
int g[N][N<<1];
FOR(i,0,R)if(g[n][i]<g[n][id2])id2=i;

输出:

int n,id1,id2;
int f[N][N],g[N][N<<1];
cout<<f[n][id1]<<" "<<f[n][id1]-g[n][id2]<<endl;

同时,题目中还有不成立的情况,那么加入特判:

int n,L;
int l[N];
FOR(i,n,1)if(l[i]-l[i-1]>L)return (cout<<"Poor Coke!"<<endl),0;

输出也要更改为:

int n,id1,id2;
int f[N][N],g[N][N<<1];
if(g[n][id2]>=INF)cout<<"Poor Coke!"<<endl;
else cout<<f[n][id1]<<" "<<f[n][id1]-g[n][id2]<<endl;

完整代码

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define max(a,b) ((a)<(b)?(b):(a))
#define min(a,b) ((a)>(b)?(b):(a))
#define tomax(a,b) ((a)=max((a),(b)))
#define tomin(a,b) ((a)=min((a),(b)))
#define RCL(a,b,c,d) memset((a),(b),sizeof(c)*(d))
#define FOR(i,a,b) for(register int i=(a);i<=(b);++i)
#define DOR(i,a,b) for(register int i=(a);i>=(b);--i)
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main
using namespace std;
const int N=2e3+10;
int n,m,R,L,id1,id2;
int a[N],b[N],l[N],P[N],F[N];
int f[N][N],g[N][N<<1];
bool chosen[N];
struct Deque{
#define Pii pair<int,int> 
#define F first
#define S second
	deque< Pii > dq;
	Deque(){Clear();}
	inline void Clear(){while(!dq.empty())dq.pop_back();}
	inline void Push(Pii x){
		while(!dq.empty()&&dq.back().S>=x.S)dq.pop_back();
		dq.emplace_back(x);
	}
	inline void Update(int x){while(!dq.empty()&&dq.front().F<x)dq.pop_front();}
	inline int Query(){return dq.empty()?INF:dq.front().S;}
#undef Pii
#undef F
#undef S
}dq[N<<1];
signed main(){
	cin>>n>>m>>R>>L;tomin(R,(n<<1));
	FOR(i,1,n)cin>>a[i]>>b[i]>>l[i]>>P[i]>>F[i];
	FOR(i,n,1)if(l[i]-l[i-1]>L)return (cout<<"Poor Coke!"<<endl),0;
	RCL(f,-1,f,1),f[0][0]=0;
	FOR(i,1,n)FOR(j,0,m){
		f[i][j]=f[i-1][j];
		if(j>=a[i])tomax(f[i][j],f[i-1][j-a[i]]+b[i]);
	}
	FOR(i,1,m)if(f[n][i]>f[n][id1])id1=i;
	id2=id1;
	DOR(i,n,1)if(f[i][id2]!=f[i-1][id2])id2-=a[i],chosen[i]=1;
	RCL(g,INF,g,1),g[0][R]=0;
	dq[R].Push({0,0});
	FOR(i,1,n)FOR(j,0,R){
		if(P[i]&&j)tomin(g[i][j],g[i][j-1]+P[i]);
		dq[j+2].Update(l[i]-L),tomin(g[i][j],dq[j+2].Query()+F[i]);
		if(chosen[i])dq[j].Clear();
		dq[j].Push({l[i],g[i][j]});
	}
	id2=0;
	FOR(i,0,R)if(g[n][i]<g[n][id2])id2=i;
	if(g[n][id2]>=INF)cout<<"Poor Coke!"<<endl;
	else cout<<f[n][id1]<<" "<<f[n][id1]-g[n][id2]<<endl;
	return 0;
}

时间复杂度 \(O(n^2)\),足够通过。


posted @ 2024-05-02 20:44  Add_Catalyst  阅读(11)  评论(0)    收藏  举报