NWERC 2020 A Atomic Energy(贪心+背包问题)
题意
有n种基本粒子,第i种基本粒子含有i个中子,对应的能量为\(a_i\)。对于一个粒子裂变释放的能量按照如下规则计算:
- 若该粒子的中子数x不超过n,则释放能量为\(a_x\)
- 否则,该粒子分裂成两个粒子,若两个粒子的中子数分别为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;
}