[蓝桥杯 2024 国 A] 最强策略家 的题解
在解题之前提交题解的代码会导致封禁。
作者:
概述
本题要求对每组数据求出在 的零矩阵上双方均采取最优策略时,游戏过程中值为
的元素个数的最大值。核心解法是二分出最大的轮数
,使先手在第
轮落子后能布下
颗满足「任意
子矩阵内至多一颗」的棋子,答案即
;
时需要单独处理。
分析
核心观察
只要棋盘上还有棋子,小乔每一轮都至少能抹掉一颗,因此小蓝每个回合最多让棋子数增加 ;只有把小蓝的棋子布成「任意
子矩阵内至多一颗」时,小乔第
轮才恰好只能抹掉一颗,棋子数才可能逐轮积累。这样的布局在
棋盘上最多能放
颗棋子。
思路
记 为小蓝第
个回合落子后棋盘上
的个数,显然
。小乔第
个回合只能选一个
子矩阵,她自然选其中棋子最多的那个;由于
时每颗棋子都落在某个
子矩阵内,故只要
她就至少能抹掉一颗,于是
若棋盘的布局满足「任意 子矩阵内的棋子都不超过一颗」(下称
-稀疏),小乔第
轮就恰好只能抹掉一颗,此时上式取等号。
-稀疏布局的规模上限为
。上界:把
行按连续的
行一组划分,共
组,列同理;同一「行组与列组」的乘积中的任意两个格子,都能被某段连续的
行与某段连续的
列同时包含,即落在同一个
子矩阵内,因此每个组对至多贡献一颗棋子,总数不超过
。下界:在行
与列
的所有交点处各放一颗,任意
子矩阵的
行中至多含一个这样的行下标,列同理,故每个
子矩阵内恰好至多一颗,共可放
颗。
取满足
的最大整数 。对
,先手都可以让小蓝第
个回合落子后的布局既
-稀疏又有
颗棋子(
);小蓝每一轮多出的那次落子可以落在已有棋子的格子上,等价于不落子,所以这种逐轮累计的方案总可以执行。棋盘自始至终是确定的,小蓝可以在开局前就定好每一轮的目标布局,故小乔无法打乱这个方案:第
轮落子后有
颗,小乔抹掉一颗剩
颗,小蓝第
轮再落两颗,棋盘上就有
颗,故
。
再证 。若
,由
与
得
。对
归纳:
;设
,若
则
;若
,则由
的最大性
,说明布局不是
-稀疏的,某个
子矩阵内至少有
颗棋子,小乔抹掉至少两颗后
。于是整个过程中棋子数始终不超过
,结合下界得
。
判定式右端是 而不是
:小蓝第
轮落子后棋盘上的棋子数是
而不是
。判定
关于
单调(左端单调不增、右端单调递增),故可以对
二分。当
时不存在满足判定的
,公式失效:此时棋盘只有一格,小蓝连两个不同的元素都选不出来,故
。
具体示例
样例第一组 :
时
成立;
时
不成立。故
,答案
,与样例一致。
样例第二组 :依次检验
,左端为
,右端为
,前三个满足、第四个不满足,故
,答案
,与样例一致。也就是说,小蓝在前三轮把棋子布成
-稀疏的
颗,第四轮落子后有
颗,但在第
轮里
,小乔必然能一次抹掉两颗,棋子数回落。
算法步骤
- 读入
T。 - 对每组数据读入
n。 - 若
n等于,输出
,处理下一组。
- 否则令
lo = 1、hi = n,循环执行:取上中位数mid = (lo + hi + 1) / 2,令c = (n + mid - 1) / mid;若c * c >= mid + 1则令lo = mid,否则令hi = mid - 1。 - 循环结束时
lo即为满足判定的最大整数,输出lo + 2。 - 把所有答案一次写出。
复杂度分析
- 时间:每组数据对
二分
次,每次判定为常数次整数运算,总计
。
- 空间:除输出缓冲外为
。
实现注意事项
- 判定式中的平方可达
(
时
可取到
),C++ 必须用
long long存储n与c,用int会溢出。 n = 1必须特判为:此时既不存在满足判定的
(输出的
会偏大),先手也无法选出两个不同的元素。
- 二分必须取上中位数
(lo + hi + 1) / 2:若取下中位数,当lo与hi相邻且lo可行时区间不再收缩,会死循环。 - 二分的上界取
n已经足够:由与
可得
,故可行解必然不超过
;又因
时
一定可行,二分区间总含有解。
- 判定只需一次整数除法和一次乘法,
ceil(n / mid)用(n + mid - 1) / mid计算,避免浮点误差。 可达
,需整体读入并一次性输出(C++ 用
fwrite,Python 用sys.stdin.buffer.read()与b"\n".join(...)),避免逐行输出带来的开销。- 多组数据之间没有依赖,逐组独立二分即可,不需要预处理或缓存。
源代码
#include <cstdio>
#include <iostream>
#include <string>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int T = 0;
if (!(std::cin >> T)) return 0;
std::string out;
out.reserve(static_cast<std::size_t>(T) * 9);
while (T--) {
long long n = 0;
std::cin >> n;
if (n == 1) { // 只有一格,先手最多只能放 1 颗棋子
out += "1\n";
continue;
}
long long lo = 1, hi = n;
while (lo < hi) {
long long mid = lo + (hi - lo + 1) / 2; // 上中位数
long long c = (n + mid - 1) / mid; // ceil(n / mid)
if (c * c >= mid + 1) {
lo = mid;
} else {
hi = mid - 1;
}
}
out += std::to_string(lo + 2);
out += '\n';
}
std::fwrite(out.data(), 1, out.size(), stdout);
return 0;
}
import sys
def main():
data = sys.stdin.buffer.read().split()
t = int(data[0])
out = []
for i in range(1, t + 1):
n = int(data[i])
if n == 1: # 只有一格,先手最多只能放 1 颗棋子
out.append(b"1")
continue
lo, hi = 1, n
while lo < hi:
mid = (lo + hi + 1) >> 1 # 上中位数
c = -(-n // mid) # ceil(n / mid)
if c * c >= mid + 1:
lo = mid
else:
hi = mid - 1
out.append(b"%d" % (lo + 2))
sys.stdout.buffer.write(b"\n".join(out) + b"\n")
main()
评论