NWERC 2020 A Atomic Energy(贪心+背包问题)

传送门

题意

有n种基本粒子,第i种基本粒子含有i个中子,对应的能量为\(a_i\)。对于一个粒子裂变释放的能量按照如下规则计算:

  1. 若该粒子的中子数x不超过n,则释放能量为\(a_x\)
  2. 否则,该粒子分裂成两个粒子,若两个粒子的中子数分别为i,j,需要满足\(i,j \geq 1\) 并且 \(i + j = x\)。两个粒子继续进行裂变

进行\(q\)次询问,每次询问对于中子数为k的粒子最少能释放多少能量?

数据范围

\(1 \leq n \leq 100\)
\(1 \leq k,a_i \leq 10^9\)
\(1 \leq q \leq 10^5\)

题解

如果把中子数看作物品体积,释放能量看作物品价值,这个题目就很类似一个完全背包问题。但还是有不同,在容量小于等于n时,与完全背包不同,原子不能继续分裂下去。
但是,我们对于一个中子数大于n的粒子,分裂过程中一定会出现一次分裂使得分裂的两个粒子\(1\leq i,j \leq n, n + 1\leq i + j\leq 2 * n\)
于是我们可以枚举这样的i+j,保证i+j在最后进行一次分裂,剩下的\(k-i-j\)就可以自由分裂出去,这样\(k-i-j\)等同于于标准的完全背包问题,这边i+j最多枚举n次,时间复杂度为\(O(nk)\)
但是k的范围较大,无法保证复杂度。
\(\color{red} {1. 如果控制复杂度?}\)
这里有一个贪心思路,对于极大的k,我们可以先优先每次选用性价比最优的物品,将k降低到一个合理的范围,再进行背包dp。
\(\color{blue} {2. 如何定义性价比?}\)
定义\(\frac{a_i}{i}\)为性价比,性价比最优的物品应该是\(\frac{a_i}{i}\)最小的物品。
\(\color{purple}{3. 如何确定这个范围呢?}\)
\(k - i -j\)\(b\),性价比最优的物品中子数为m,我们假设b的最优分解方案为\(b = z\cdot m + \sum_i^N x_i(x_i\neq m)\),产生的能量为\(z\cdot a[m] + \sum_i^N a[x_i]\)
\(N>m\),则一定存在若干个\(x_i\)之和为m的倍数,即\(\sum_{s \in S} x_i = c\cdot m\),可以用鸽笼定理进行证明:
定理:不小于m个元素的非负整数集合,一定存在一个非空子集的元素和为m的倍数。
对该集合求一个前缀和,并对前缀和对m取模,根据鸽巢原理,一定有两个数膜意义下相同或者某个前缀和膜意义下为0,即该子集一定为m倍数。
证毕
对于\(\sum_{s \in S} x_i = c\cdot m\),其产生的能量为\(\sum_{s\in S} a[x_i]\geq c\cdot a[m]\)(根据性价比的不等式\(\frac{a_i}{i}\geq \frac{a_m}{m}\))。
故N不会超过m,即\(\sum_i^N x_i(x_i\neq m)\leq m\cdot n\),最终可以通过选择性价比最优的原子m,将k压缩到\(m\cdot n\)的范围,时间复杂度为\(O(n^3+n\cdot q)\)

代码

/*************************************************************************
	> File Name: 1.cpp
	> Author: Knowledge_llz
	> Mail: 925538513@qq.com 
	> Blog: https://www.cnblogs.com/Knowledge-Pig/ 
	> Created Time: 2022/1/19 14:05:27
 ************************************************************************/

#include<bits/stdc++.h>
#define LL long long
#define endl '\n'
using namespace std;
LL n, q, a[2021], dp[2021 * 2021];
int main(){
	ios::sync_with_stdio(false); cin.tie(0);
#ifndef ONLINE_JUDGE
	freopen("input.in", "r", stdin);
	freopen("output.out", "w", stdout);
#endif
	cin >> n >> q;
	for(int i = 1; i <= n * n + n; ++i) dp[i] = 1e18;
	LL m = 1;
	for(int i = 1; i <= n; ++i){
		cin >> a[i]; a[i + n] = 1e18;
		if(a[i] * m < a[m] * i) m = i;
		for(int j = i; j <= n * n + n; ++j)
			dp[j] = min(dp[j], dp[j - i] + a[i]);
	}
	for(int i = 1; i <= n; ++i)
		for(int j = n - i + 1; j <= n; ++j)
			a[i + j] = min(a[i + j], a[i] + a[j]);
	while(q--){
		LL x;
		cin >> x;
		if(x <= n) cout << a[x] << endl;
		else{
			LL ans = 1e18;
			for(int i = n + 1; i <= n * 2; ++i){
				LL y = x - i;
				if(y < 0) continue;
				LL z = max(0ll, (y - n * n)) / m;
				y -= z * m;
				ans = min(ans, z * a[m] + dp[y] + a[i]);
			}
			cout << ans << endl;
		}
	}
	return 0;
}
posted @ 2022-01-20 11:16  Knowledge-Pig  阅读(62)  评论(0编辑  收藏  举报