CF1428G2 Lucky Numbers (Hard Version)
Part 1:一些发现
首先我们注意到,每个数位可以几乎独立地计算贡献,不同数位的之间的联系只在于总和 \(n\)。
同时还容易发现,把两个同数位上的 \(3,3\) 或 \(3,6\),合并成一个必定不劣,可能更优。
比如说:假若存在两个数同数位上分别是 \(3,3\),我们将它们合并成一个 \(6\) 必定不劣,剩下的一个数有了更大的空间。\(3,6\) 也同理。
Part2:维护方法
考虑 \(n\) 不大,考虑直接将 \(n\) 记进状态 DP。记 \(f_{i,j}\) 表示,考虑了前 \(i\) 高位,当前总大小为 \(j\) 的最大权值。
一个理想策略是,所有当前数位上全是 \(3\) 的倍数。然而这不现实——因为题目要求选满 \(n\),而不是要求总和 \(\leq n\)。
对此,我们退而求其次,留一个空位给非 \(3\) 的倍数。这样的正确性是因为任意两个不是 \(3\) 倍数的数,必定可以调整出至少一个 \(3\) 的倍数。
每次转移枚举这样的空位上填什么。对于剩下的位置,假若把题目给的 \(F_i\) 看做一个物品的话,至多能有 \(3(k-1)\) 个物品,做二进制分组即可。
时间复杂度 \(O(10n\log k)\),常数极小,容易通过。
Part3:代码
#include <bits/stdc++.h>
#define FL(i, a, b) for (int i = (a); i <= (b); ++i)
#define FR(i, a, b) for (int i = (a); i >= (b); --i)
#define fi first
#define se second
#define eb emplace_back
#define Sz(v) ((int)(v).size())
using namespace std;
typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> pii;
const int Nn = 1e6, N = Nn + 10;
const ll INFLL = 1e18;
int k, q;
ll F[6], f[7][N];
int pw[] = {1, 10, 100, 1000, 10000, 100000, 1000000};
template<typename T>
void ChkMax(T &x, const T &y) {
if (y > x) x = y;
}
void DP() {
FL(i, 0, 6) {
fill(f[i], f[i] + Nn + 2, -INFLL);
}
vector<int> t;
{
int tk = min(Nn, (k - 1) * 3);
for (int i = 1; i <= tk; i <<= 1) {
t.emplace_back(i);
tk -= i;
}
if (tk) t.emplace_back(tk);
}
f[6][0] = 0;
FR(i, 5, 0) {
FL(j, 0, Nn) {
FL(k, 0, 9) {
if (f[i + 1][j] < 0) continue;
ll tj = j + (ll)k * pw[i];
ll tc = f[i + 1][j] + (k % 3? 0 : (k / 3) * F[i]);
ChkMax(f[i][tj], tc);
}
}
for (int x: t) {
ll R = Nn - (ll)x * pw[i] * 3;
if (R < 0) continue;
FR(j, R, 0) {
ll nj = j + (ll)x * pw[i] * 3;
ChkMax(f[i][nj], f[i][j] + x * F[i]);
}
}
}
}
int main() {
scanf("%d", &k);
FL(i, 0, 5) {
scanf("%lld", &F[i]);
}
DP();
scanf("%d", &q);
while (q--) {
int x;
scanf("%d", &x);
printf("%lld\n", f[0][x]);
}
return 0;
}