4 条题解
-
0
满分解法(100 分)
从一次得分看卡牌的用途
得分后只留下刚放入的一张牌。因此,第一次得分至少要放三张牌,此后每次得分至少再放两张牌。另一方面,保留下来的牌会参与下一次得分,同一张牌可以被利用两次,不能把每次得分理解成互不相交的三张牌。
例如,将两张黑牌、两张白牌和一张青牌按“黑、白、青、黑、白”放入:第三张牌触发第一次得分并留下青牌,第五张牌触发第二次得分。同一张青牌参与了这两次得分。
三种颜色地位相同。将数量排序,记为 ,对应颜色记为 ,总张数为 。先求得分的上界,再证明这些上界的最小值一定能够达到。
三个独立的上界
假设获得 分。
总张数。 第一次得分至少使用三张新牌,以后每次至少使用两张新牌,所以
$$2k+1\le N,\qquad k\le\left\lfloor\frac{N-1}{2}\right\rfloor.$$最少的一种颜色。 一张牌第一次参与得分时,只有它恰好是刚放入的牌,才会被保留;它再次参与得分时必然被销毁。因此每张牌至多参与两次得分,而每次得分都需要颜色 ,所以
最少的两种颜色。 每次得分至少销毁一张 或 色牌。观察最后一次得分:如果最后留下 色牌,这次会同时销毁至少一张 和一张 ,总共至少销毁 张这两种颜色的牌;如果最后留下 或 色牌,至少有 张这两种颜色的牌被销毁,外加最后留下的一张。两种情况都要求
第三个上界不能省略。例如 时,前两个上界分别是 和 ,但实际只能得 分:第一次得分后,两种稀缺颜色至少有一种已经耗尽。
因此答案不超过
$$K=\min\left(\left\lfloor\frac{a+b+c-1}{2}\right\rfloor,\ 2a,\ a+b-1\right).$$用相邻得分之间的共享牌构造方案
下面证明任意满足三个上界的正整数 都可达到。 时直接放入三种颜色各一张即可,以下考虑 。
把每次得分所需的三色牌各记为一个三元组。让相邻两个三元组共享前一次得分留下的那张牌,这样总共只使用 张实体牌。共有 张共享牌,将它们依次记为 。
相邻共享牌的颜色必须不同:在一次中间得分中,前一次留下的牌已经在堆中,后一次留下的牌必须是另外一种颜色,才能补齐三色。反过来,只要共享牌序列没有相邻同色,就能恢复合法投放:第一个三元组先放另两色,再放 ;中间三元组在已有 的基础上,先放第三种颜色,再放 ;最后一个三元组放入当前缺少的两种颜色。
设三种颜色各被选作共享牌 次,则实际使用的三色牌数分别为
因此,我们要找到总和为 的非负整数 ,满足
$$t_A\ge\ell_A=\max(0,k-a),\quad t_B\ge\ell_B=\max(0,k-b),\quad t_C\ge\ell_C=\max(0,k-c),$$并且能排成没有相邻同色的共享牌序列。
长度为 的序列能够这样排列,当且仅当每种颜色的数量都不超过 。必要性来自相同颜色之间要用别的颜色隔开。充分性可以这样构造:按颜色数量从多到少,依次填入第 个位置,再填第 个位置。同一段内同色牌隔位出现;跨越奇偶两段的那种颜色数量严格小于 ,它占用的奇数位置和偶数位置之间仍隔着至少两个位置,不会相邻。数量等于 的颜色会完整占满首段,或在首段填完后才开始,不会跨段。
接下来检查三个上界恰好保证这些要求。
上界保证共享牌数量能够分配
首先,由 及 ,每个计数下界都不超过 。具体地,,因此
$$\ell_A\le k-\left\lceil\frac{k}{2}\right\rceil =\left\lceil\frac{k-1}{2}\right\rceil=h,$$而 。
其次,需要证明 。按正下界的数量分类:
- 三个都为正时,总和为 。由 ,它不超过 。
- 两个为正时,必然对应 ,总和为 。由 ,它不超过 。
- 只有一个为正时,总和为 ;没有正下界时总和为 。
从三个下界出发,逐个增加计数,直到总和达到 ,过程中不让任何计数超过 。因为下界之和不超过 ,而总容量 ,这个补足过程一定可以完成。
这样得到的共享牌序列可以按上一节恢复成合法的 次得分,且每种颜色实际使用的牌数都不超过现有数量。最后把未使用的牌继续放入牌堆,不会减少已经获得的分数。因此 不仅是上界,也确实可达。
计算答案
排序三个数量,直接计算
$$\boxed{\min\left(\left\lfloor\frac{a+b+c-1}{2}\right\rfloor,\ 2a,\ a+b-1\right)}.$$构造只用于证明,程序不需要实际生成投放顺序。
时间复杂度:,每组只排序三个数并计算三个表达式。
空间复杂度:,各组独立处理,只保存三个数量。
AC 代码(C++20)
#include <bits/stdc++.h> using namespace std; using i64 = long long; void solve() { vector<i64> a(3); for (i64& x : a) cin >> x; sort(a.begin(), a.end()); i64 sum = accumulate(a.begin(), a.end(), 0LL); cout << min({(sum - 1) / 2, 2 * a[0], a[0] + a[1] - 1}) << '\n'; } int main() { cin.tie(0)->sync_with_stdio(0); int T; cin >> T; while (T--) solve(); } -
0
两种较多颜色等量(通过子任务 1、5–6,共 40 分)
两种较多颜色等量时的上界
考虑 的情况,包含子任务 。记 。这里黑牌数量最少,白牌与青牌等量。
每次得分后的保留牌可以再参加一次得分,但第二次之后必定被销毁。因此一张黑牌至多参与两次得分,答案不超过 。第一次得分至少需要三张新牌,此后每次至少需要两张新牌,所以答案还不超过 。
下面证明在这个等量条件下,两项限制已经足够,答案就是
$$K=\min\left(2x,\left\lfloor\frac{x+2y-1}{2}\right\rfloor\right).$$证明两个上界可以同时达到
时放入三色各一张即可。设 ,把目标的 次得分各记为一个三色三元组。相邻两个三元组共享前一次留下的实体牌,共有 张共享牌;相邻共享牌颜色不同,才允许中间三元组先使用旧的保留牌,最后留下另一种颜色。
若某种颜色被共享 次,它在 个三元组中的实际用量就是 。所以黑色共享次数至少为 ,白色和青色的共享次数分别至少为 。因为 ,有 。
没有相邻同色的长度为 的序列要求每色最多出现 次。由 ,得到 ,因此 。
这些下界的总和也不会超过 :若 ,只有黑色可能需要共享,;若 ,三色都需要共享,此时
最后一步正是利用总数上界 。
从三个下界开始补足到总数 ,每个计数都不超过 。总容量 ,所以一定能补足。这样的三色计数可以排成没有相邻同色的序列:按颜色数量降序,依次填奇数位置、再填偶数位置。同色在各段内隔位出现,跨段的颜色数量小于 ,两段占据的位置不会相邻。
得到共享牌序列后,第一组先放另外两色、最后放第一张共享牌;中间每组在旧共享牌之后,先放第三色、最后放下一张共享牌;最后补入缺少的两色。这样获得 分,各色用量都不超过现有数量,剩余牌最后继续放入即可。
这个做法依赖 。例如 不满足该条件,两个上界取最小会得到 ,实际却只有 分;因此不能把本式直接用作一般情况的答案。
当 时,式子简化为 ,所以相同代码自然覆盖等量子任务,无需再写一份算法。
时间复杂度:。空间复杂度:。
参考代码(C++20)
#include <bits/stdc++.h> using namespace std; using i64 = long long; void solve() { i64 x, y, z; cin >> x >> y >> z; cout << min(2 * x, (x + y + z - 1) / 2) << '\n'; } int main() { cin.tie(0)->sync_with_stdio(0); int T; cin >> T; while (T--) solve(); } -
0
按剩余数量记忆化(通过子任务 1–4,共 51 分)
只保留有用的投放
在 时,可以按三色剩余数量记忆化搜索,覆盖子任务 。
第一次得分需要三种颜色各一张,最后放哪一种就留下哪一种。一次得分以后,牌堆中只有一张牌。为了再次得分,只需要再放入另外两种颜色各一张,并选择其中哪一种最后放入。
为什么可以不考虑提前放入的多余牌?在任意投放方案中,两次得分之间重复放入同一种颜色,只会使它们在下一次得分时一起销毁;保留每个必需颜色的一张,并维持原本最后放入的颜色,仍会产生相同的得分和相同的保留牌。所有删去的投放移到已有得分完成之后即可。因此,存在一个最优方案,第一次恰用三张新牌,此后每次恰用两张新牌。
状态与转移
将黑、白、青依次编号为 。定义 为:还有 张牌尚未投放,牌堆中恰有一张颜色 的牌时,之后最多还能获得的分数。保留牌不计入 。
如果 ,下一次得分必须各取一张白牌、青牌,可以最后放白牌或青牌,所以
$$f(x,y,z,0)=1+\max\{f(x,y-1,z-1,1),f(x,y-1,z-1,2)\}\quad(y,z>0).$$同理,另外两种保留颜色的转移为
$$f(x,y,z,1)=1+\max\{f(x-1,y,z-1,0),f(x-1,y,z-1,2)\}\quad(x,z>0),$$$$f(x,y,z,2)=1+\max\{f(x-1,y-1,z,0),f(x-1,y-1,z,1)\}\quad(x,y>0).$$当对应的两个剩余数量中有一个为 时,无法再凑齐三色,状态值为 。其余未计算状态用 标记。
每次递归使剩余总数减少 ,因此没有循环。同一状态以后的选择只由三个剩余数量和保留颜色决定,与之前的投放顺序无关,可以直接记忆化。
第一次用掉三色各一张并得到一分,保留颜色可以任选,最终答案为
例如 ,第一次留下青牌后到达 ,还能取一张黑牌和一张白牌,再得一分,总分为 。第一次留下黑牌时,剩余青牌为零,就不能继续得分。
复杂度
初次得分后三个坐标范围分别为 、、,保留全部 个状态,每个状态只有两个后继。按测试用例分别建立、释放状态表。
时间复杂度:,包括状态表初始化。
空间复杂度:,状态表和递归栈均计入。大计数下此表无法存储,方法只保证上述受限子任务。
参考代码(C++20)
#include <bits/stdc++.h> using namespace std; using i64 = long long; void solve() { int x, y, z; cin >> x >> y >> z; vector dp(3, vector(x, vector(y, vector<int>(z, -1)))); auto dfs = [&](auto&& self, int x, int y, int z, int color) -> int { int& res = dp[color][x][y][z]; if (res != -1) return res; res = 0; if (color == 0 && y && z) { res = 1 + max(self(self, x, y - 1, z - 1, 1), self(self, x, y - 1, z - 1, 2)); } if (color == 1 && x && z) { res = 1 + max(self(self, x - 1, y, z - 1, 0), self(self, x - 1, y, z - 1, 2)); } if (color == 2 && x && y) { res = 1 + max(self(self, x - 1, y - 1, z, 0), self(self, x - 1, y - 1, z, 1)); } return res; }; int ans = 0; for (int i = 0; i < 3; i++) ans = max(ans, dfs(dfs, x - 1, y - 1, z - 1, i)); cout << ans + 1 << '\n'; } int main() { cin.tie(0)->sync_with_stdio(0); int T; cin >> T; while (T--) solve(); } -
0
枚举投放顺序(通过子任务 1–2,共 11 分)
当 时,可以枚举全部投放顺序,覆盖子任务 。
同色卡牌没有区别。建立一个包含 个 、 个 、 个 的升序序列,用全排列枚举每一种不同的颜色排列。对每个排列从空牌堆开始模拟,记录当前牌堆中出现的颜色集合以及已经获得的分数。
用三位二进制数表示颜色集合。放入颜色 后,将第 位置为 。集合变成 时增加一分,并将集合改为只包含刚放入的颜色,即 。这里不能清空集合,因为新牌仍留在堆中。
例如“黑、白、青、黑、白”的集合依次为 ,第三步和第五步各得一分;两次得分后的集合都只保留最后一种颜色。
每种合法投放顺序恰好对应一个颜色排列,模拟又完全执行题面规则,所以对所有排列得分取最大值就是答案。
记 ,不同排列的数量为 。虽然 时也能用相同方法算出正确答案,但排列数量增长很快,在最大 下不能保证原时限。
时间复杂度:$O\!\left(\sum_{t=1}^{T}N_t\frac{N_t!}{X_t!Y_t!Z_t!}\right)$,每个排列扫描全部卡牌。
空间复杂度:,各组依次处理,仅保存当前颜色排列。
参考代码(C++20)
#include <bits/stdc++.h> using namespace std; using i64 = long long; void solve() { vector<int> cnt(3), a; for (int& x : cnt) cin >> x; for (int i = 0; i < 3; i++) a.insert(a.end(), cnt[i], i); int ans = 0; do { int mask = 0, sum = 0; for (int x : a) { mask |= 1 << x; if (mask == 7) { sum++; mask = 1 << x; } } ans = max(ans, sum); } while (next_permutation(a.begin(), a.end())); cout << ans << '\n'; } int main() { cin.tie(0)->sync_with_stdio(0); int T; cin >> T; while (T--) solve(); }
- 1
信息
- ID
- 2310
- 时间
- 1000ms
- 内存
- 512MiB
- 难度
- 9
- 标签
- (无)
- 递交数
- 93
- 已通过
- 20
- 上传者