NWERC2020 A - Atomic Energy (背包 + 思维)

题意:
给你\(n\)个神经元,从\(1 - n\)编号,我们可以认为编号为\(i\)的神经元的体积为\(i\),带有\(a_i\)的能量。
然后一共有\(q\)个询问,每个询问带有一个\(k\),你需要回答,当一个体积为\(k\)的神经元分裂是会释放多少能量,能量释放按照以下规则:
一个体积为\(k\)的神经元,每次可以分解为两个体积分别为\(i,j\)的神经元,需要保证\(i + j = k\),当一个神经元体积\(v \leq n\)时,他将立即释放\(a_v\)的能量。每次询问,你需要回答能释放的最小的能量为多少。

思路:
首先,我们看,体积和能量,这两个参数是不是很像背包的问题。所以我们可以往这个方面考虑一下,但是这道题有点不同。
首先,我们分裂其实是有一个限制条件的,即不可能出现某个\(\leq n\)的数继续分裂,所以其实我们最后一定要留出两个体积\(v_1,v_2\)并且保证他们相加\(v_1 + v_2 > n\),至于剩余的体积\(K - v_1 - v_2\)就会有任意的分解方法,所以我们需要留出一个体积在\([n + 1,n * 2]\)的神经元来作分裂。

下一步再看,给定的\(K\)的范围,有点太大了也,肯定没法直接\(dp\),能不能把一个大范围贪心处理到一个小范围,我们考虑找到一个神经元使其"性价比"最好,即这个神经元体积尽量的大,同时能量尽量的小也就是\(\frac{a_i}{i}\)最小,其体积记为\(best\),则对于很大的\(k\)只需要让其减少若干的\(best\)掉入一个我们可接受的范围区间内,直接\(dp\)处理。

那这个区间的范围怎么确定呢,这里需要用到一个结论。
假如我现在有\(x\)个数,那么我从中选择若干个一定可以组成\(x\)的倍数。
证明来自b站某大佬视频:
既然是倍数,那么考虑这\(x\)个数都在模\(x\)意义下的取值,设\(s_i\)表示前\(i\)个数的前缀和在模\(x\)意义下的取值,很明显,\(x + 1\)个位置(什么不选即为0),模\(x\)最多有\(x\)个不同的取值,根据鸽巢原理(抽屉原理),那么一定会有两个位置\(i,j\)处的\(s_i = s_j\),即他们之间的数是\(x\)的倍数。

至此,我们可以知道,如果有超过\(\geq x\)个数,那么我们就可以选择其中若干个用\(best\)消去,直至总量\(< x\),所以我们需要预处理的范围其实就是[1,(best-1) * n],(best - 1)保证不会被消去,每个最大体积为\(n\),这样就可以通过\(O(n^3)\)的\(dp\)解决了。

这道题挺好的,有两个思维点,转换之后就是比较普通的背包了,值得积累,以后碰到范围很大的背包问题,可以考虑是否能缩小到一定范围内再去\(dp\),大范围直接贪心求解。

#include <bits/stdc++.h>

using namespace std;

#define pb push_back
#define eb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
#define CLOSE std::ios::sync_with_stdio(false)
#define sz(x) (int)(x).size()
typedef long long ll;
typedef double db;
const ll INF = 0x3f3f3f3f;
const db eps = 1e-6;
const int N = 1e6 + 10;
int n,q;
ll a[N],dp[N];

int main() {
	scanf("%d%d",&n,&q);
	int best;
	double mi = 1.0 * INF;//记录那个物品的性价比是最优的
	for(int i = 1;i <= n;i ++) {
		scanf("%lld",&a[i]);
		if(1.0 * a[i] / i < mi) {
			mi = 1.0 * a[i] / i;
			best = i;
		}
	}
	memset(dp,INF,sizeof(dp));
	for(int i = n + 1;i <= 2 * n;i ++) {
		for(int j = 1;j <= n;j ++) {
			if(i - j <= n) 
				dp[i] = min(dp[i],a[j] + a[i - j]);
		}
	}
	for(int i = n + 1;i <= n * (n + 1);i ++) {
		for(int j = 1;j <= n;j ++) {
			if(i - j > n) {
				dp[i] = min(dp[i],dp[i - j] + a[j]);
			}
		}
	}
	// for(int i = n + 1;i <= k;i ++) {
	// 	cout << dp[i] << '\n';
	// }
	ll k;
	while(q--) {
		scanf("%lld",&k);
		if(k <= n) {
			printf("%lld\n",a[k]);
		}
		else {
			if(k > n * (best + 1)) {//是值域落至可处理范围内 即k - j * best ∈ [1,(best-1)*n] (j = 1,2,...)
				ll num = (k - n * (best + 1) + best - 1) / best;
				ll ans = num * a[best] + dp[k - num * best];
				printf("%lld\n",ans);
			}
			else {
				printf("%lld\n",dp[k]);
			}
			// ll num = max(0LL,(k - n * (best + 1) + best - 1) / best);
			// ll ans = num * a[best] + dp[k - num * best];
			// printf("%lld\n",ans);
		}
	}
	return 0;
}
上一篇:PVPC代码的运行脚本


下一篇:【优化算法】Tent混沌映射的粒子群算法【含Matlab源码 940期】