952 字
5 分钟
题解 CF2157F Git Gud
2026-04-26
无标签

题意#

你有10000001'000'000块钱, 当前等级为[1,250000][1, 250'000]内的任意整数.

需要构造一个(y,l)(y, l)的任务序列. 对于每个任务的花费是: 若本任务的y比前一个任务的y大, 则为l+1000l + 1000, 否则为ll. 每个任务的收益是: 若当前技能等级等于yy, 则技能等级提升ll. 要求总花费不超过初始财产的限制下, 这个操作序列能将任意的初始等级提升到至少nn.

分析#

总花费是多少? 实际上就是任务序列所有的ll总和, 再加上yy提升的次数乘以10001'000.

这是个构造题, 先猜一猜一些可能的解. 比如我们构造1,2,3...,n11, 2, 3..., n - 1这个难度序列, ll总和为249999249'999, 然而yy提升了非常多次(249998249'998), 每次都要花10001'000, 这显然是非常不划算的.

如果我们这样构造呢? n1,n2,n3,...,1n - 1, n - 2, n - 3, ..., 1. 这样显然上升的次数是0, 但是由于没有地方中转, 每个任务我们都得将y提升到nn. ll的总和将会是n2n^2级别的, 不可接受.

所以说, 我们要让总花费最小, 重点在于平衡上升的次数与ll的总和.

如果说我们这样构造呢? n1,n3,n5...n - 1, n - 3, n - 5..., 每次上升1, 相当于把所有技能等级收集到了偶数上.再进行n2,n4,n6...n - 2, n - 4, n - 6...我们会发现, 虽然上升的次数多了一次, 然而由于我们提供了偶数作为中转, ll的总和下降了非常多(虽然离我们的目标还很远). 进一步, 如果我们再把偶数收集到4倍数上, 4倍数收集到8倍数上…这样虽然上升的次数会增加, 但是总和会下降, 我们能不能找到一个比较好的极值呢? 很不幸并不能, 因为题目的限制非常严格. 然而我们很快会发现, 以上我们默认了”2”为基数, 而事实上”2”并非最好的基数.

解法#

我们假设基数为mm. 这时候我们需要收集logmn\log_m n趟. 对于每趟来说: 先把所有模mm为1的数收集到模mm为2的数上, 再把模mm为2的数收集到模mm为3的数上…最后收集到模mm为0的数上, 这趟就算是跑完了. 我们会发现每一趟来说, 每个任务的ll都是1, 且几乎每个技能等级都会涉及一次(除了那些模m为0的技能等级). 所以, 每一趟中ll的总和我们可以看作nn. 每一趟上升多少次呢? 实际上就是收集的次数, 至多为mm.

所以我们可以列出总花费:

logmn(1000m+n)\log_m n(1000m + n)

注意到logmnlog_m n不会有太多值, 我们可以手算枚举logmn\log_m n的值为1,2,3...1, 2, 3...时的情形, 得到logmn=3log_m n = 3的时候总花费可以低于10000001'000'000(当然你想求导也是可以的). 此时m=n1/363m = n^{1 / 3} \approx 63. 于是我们就解决了这个问题.

AC代码#

提交记录

void solution() {
/* code here */
int n;
cin >> n;
if (n == 4) {
cout << R"(4
1 4
3 1
2 1
3 1
)";
return;
}
ll m = 63;
ll base = 1;
vector<int> vis(n + 1);
vector<pair<int, int>> ans;
ans.reserve(250000);
for (int i = 0; i < 3; i++) {
for (int j = 1; j < m; j++) {
for (int k = n; k >= 1; k--) {
if (vis[k]) {
continue;
}
if ((k / base) % m != j) {
continue;
}
vis[k] = 1;
ans.emplace_back(k, base);
}
}
base *= m;
}
cout << ans.size() << '\n';
for (const auto& [x, y] : ans) {
cout << x << ' ' << y << '\n';
}
}
题解 CF2157F Git Gud
https://fuwari.vercel.app/posts/题解-cf2157f-git-gud/
作者
ykindred
发布于
2026-04-26
许可协议
CC BY-NC-SA 4.0