「517模拟赛2」巡游

「517模拟赛2」巡游

数轴上分布着 \(n\) 个礼品店,第 \(i\) 个礼品店在位置 ,进入礼品店 后,你会购买一个重量为 \(a_i\) 的礼品(至多一次)。

你需要从 \(0\) 出发,最后返回 \(0\) 。每次可以选择购买当前位置的礼品,或者向 左/右 移动 \(1\) 单位的距离。如果你当前携带着重量之和为 \(s\) 的礼品,每次移动你需要消耗 \(s+1\) 的能量。

问在消耗不超过 \(e\) 的能量的情况下,最多能带回多少重量的礼品。

对于全部数据,满足 \(1\le n\le 10^6,1\le e\le 3\times10^7,0\le a_i\le10^6\)


对于一个给定的礼品集合,我们肯定是先走到最远的在礼品集合里的礼品店,然后返回,在返回的路上购买礼品。

那么问题就抽象成了有 \(n\) 个礼品,每个礼品的价值是 \(a_i\),每个礼品的体积是 \(a_i\times i\)。然后对于最大的那个礼品,他的体积还要加上 \(2\times i\)。所以我们可以做一遍背包,复杂度是 \(O(n\times e)\) 的。

注意到 \(a_i<a_i\times i\)。而背包的复杂度与价值无关,只与体积有关。所以我们考虑重新定义状态,定义 \(f_{i,j}\) 表示前 \(i\) 个物品,拿了总价值为 \(j\) 最小的消耗体积。但是 \(a_i\) 的值域依然很大,时间并没有很快的增加。

再考虑 \(a_i\times i\) 这个式子。我们容易发现,假设现在的总价值为 \(W\),所处的位置在 \(i\)。那么体积一定大于等于 \(W\times i\)。又因为体积要小于 \(e\)。有 \(e\le W\times i\)\(W\ge \dfrac{e}{i}\)。所以我们只用转移总价值大于等于 \(\dfrac{e}{i}\) 的情况。这仍然很大,那如果我们倒着做 dp 呢,假设我们现在的总价值为 \(W\),所处的位置在 \(i\) 。那么体积一定小于等于 \(W\times i\),就可以得到 \(W\le \dfrac{e}{i}\)。所以我们只用转移总价值小于等于 \(\dfrac{e}{i}\) 的情况。时间复杂度就等于 \(O(n+\sum_{i=1}^{n}{\dfrac{e}{i}})\),也就是 \(O(n+n\log n)\)

代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 1e6+5;
int n;
ll E,A[MAXN],ans;
ll f[30000005];
void Min(ll &x,ll y) {if(x>y) x=y;}
void Max(ll &x,ll y) {if(x<y) x=y;}
int main()
{
	freopen("walk.in","r",stdin);
	freopen("walk.out","w",stdout);
	scanf("%d %lld",&n,&E);
	for(int i=1;i<=n;++i) scanf("%lld",&A[i]);
	memset(f,0x3f,sizeof f);f[0]=0;
	for(int i=n;i>=1;--i)
	{
		for(int j=E/i;j>=A[i]+1;--j)
			Min(f[j],f[j-A[i]]+A[i]*i);
		if(E/i>=A[i]) Min(f[A[i]],f[0]+A[i]*i+2*i);
	}
	ll ans=0;
	for(ll i=0;i<=E;++i) if(f[i]<=E) Max(ans,i);
	printf("%lld\n",ans);
	return 0;
}
posted @ 2021-11-14 14:36  夜空之星  阅读(47)  评论(0编辑  收藏  举报