题目

gym 链接
动态规划 数论题意
给你 个原子,第 个原子有 个中子,以及由 的能量。
接下来由 次询问,每次输入一个 ,表示有 个中子的原子。
- 若 ,则释放对应原子的能量 ;
- 若 则会分裂成若干原子,每次分裂只会分裂出两个原子,分裂后只有满足 的原子会继续分裂,以此类推,使分裂后的每个原子的中子数小于 时结束。
求最小能量。
题解
首先不难想到贪心,若 足够大,刚开始的几次一定是选择性价比比较好的,即 尽可能小的。
但硬贪一定是有问题的,原因主要是当分裂后原子的中子数小于 ,就不会继续分裂。这就有可能存在继续贪下去得到的是局部最优解而非全局最优解。
中子数为 , 求所能获得的最小能量,不难想到这是一个背包问题。所以,设某次分裂后原子的中子数为 , ,且有 , ,对于剩下的 就贪心处理。
对于 的范围,我们要用到鸽巢原理的推论: 个数一定存在某个区间的和能 整除。
假设我们得到性价比最好的中子数为 ,选出 个原子去消去,每个的原子的最大中子数为 ,又因为最后一次分裂还有两个原子,所以预处理的区间为 。
时间复杂度为 。
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #include <iostream> #include <cstring>
using namespace std;
typedef long long ll; const int N = 1e5 + 10;
int n, q, k, p; ll ans, num; ll a[N]; ll dp[N]; double mn = 1.0 * 0x3f3f3f3f;
int main() { cin >> n >> q; for (int i = 1; i <= n; i++) { cin >> a[i]; if (1.0 * a[i] / i < mn) { mn = 1.0 * a[i] / i; p = i; } } memset(dp, 0x3f, sizeof(dp)); for (int i = n+1; i <= n * 2; i++) { for (int j = 1; j <= n; j++) { if (i - j <= n) dp[i] = min(dp[i], a[i-j] + a[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]); } } while (q--) { cin >> k; if (k <= n) { cout << a[k] << endl; } else { if (k > n * (p+1)) { num = (k - n * (p+1) + p - 1) / p; ans = num * a[p] + dp[k - num * p]; cout << ans << endl; } else { cout << dp[k] << endl; } } } return 0; }
|


yngcy
夕拾天星,曦携明露
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。