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

9 篇文章 0 订阅
4 篇文章 1 订阅

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

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

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

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

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

这道题挺好的,有两个思维点,转换之后就是比较普通的背包了,值得积累,以后碰到范围很大的背包问题,可以考虑是否能缩小到一定范围内再去 d p 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;
}
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值