[过程记录] 《分寝室》 一题做法思考
[过程记录] 《分寝室》 一题做法思考
@Author: Kai
@Time: 2025-03-20
从错误的解题思路开始,一直怀疑 L1 哪有那么难,直到使用遍历发现 10e5 并没有很大。记录下这个曲折的过程。
题目
L1-7 分寝室
学校新建了宿舍楼,共有 n 间寝室。等待分配的学生中,有女生 n0 位、男生 n1位。所有待分配的学生都必须分到一间寝室。所有的寝室都要分出去,最后不能有寝室留空。
现请你写程序完成寝室的自动分配。分配规则如下:
- 男女生不能混住;
- 不允许单人住一间寝室;
- 对每种性别的学生,每间寝室入住的人数都必须相同;例如不能出现一部分寝室住 2 位女生,一部分寝室住 3 位女生的情况。但女生寝室都是 2 人一间,男生寝室都是 3 人一间,则是允许的;
- 在有多种分配方案满足前面三项要求的情况下,要求两种性别每间寝室入住的人数差最小。
输入格式:
输入在一行中给出 3 个正整数 n0、n 1 、n,分别对应女生人数、男生人数、寝室数。数字间以空格分隔,均不超过 10 5 。
输出格式:
在一行中顺序输出女生和男生被分配的寝室数量,其间以 1 个空格分隔。行首尾不得有多余空格。
如果有解,题目保证解是唯一的。如果无解,则在一行中输出No Solution
。
输入样例 1:
24 60 10
输出样例 1:
4 6
注意:输出的方案对应女生都是 24/4=6 人间、男生都是 60/6=10 人间,人数差为 4。满足前三项要求的分配方案还有两种,即女生 6 间(都是 4 人间)、男生 4 间(都是 15 人间);或女生 8 间(都是 3 人间)、男生 2 间(都是 30 人间)。但因为人数差都大于 4 而不被采用。
输入样例 2:
29 30 10
输出样例 2:
No Solution
分析
这道题满足一个式子:
设男生寝室数量为x
,女生寝室数量为y
,那么有关系:
min ∣ 男生总数 x − 女生总数 y ∣ x + y = 房间总数 \begin{matrix} \min \left | \frac{男生总数}{x} - \frac{女生总数}{y} \right | \\ x+y=房间总数 \end{matrix} min x男生总数−y女生总数 x+y=房间总数
-
约束条件分析
根据 x + y = C x + y = C 4 x+y=Cx+y=C4 x+y=Cx+y=C4,可得 y = C − x y = C − x y=C−xy=C−x y=C−xy=C−x。因此,x 需满足:2 ≤ x ≤ A 2 ≤ y = C − x ≤ B \begin{matrix} 2≤x≤A \\ 2≤y=C−x≤B \end{matrix} 2≤x≤A2≤y=C−x≤B
即 x 的有效范围为:
x m i n = max ( 2 , C − B ) , x m a x = min ( A , C − 2 ) x_{min} = \max (2, C-B), x_{max} = \min (A, C-2) xmin=max(2,C−B),xmax=min(A,C−2)
-
遍历可行解
遍历 xx 的有效范围,计算对应的 y = C − x y=C-x y=C−x,并检查 y 是否在 [2,B] 内。对于每个有效的 (x,y),计算 $\left | \frac{A}{x}-\frac{B}{y} \right | $,记录最小值对应的解。 -
直接返回最小解
若存在多个解具有相同的最小绝对值,返回任意一个(例如第一个找到的解)。
解法
def find_min_abs_diff(A, B, C):
"""
寻找满足 x + y = C, 2 <= x <= A, 2 <= y <= B 的正整数解 (x, y),
使得 |A/x - B/y| 最小。若存在,返回 (x, y);否则返回 None。
"""
x_min = max(2, C - B) # x的下限:确保y = C - x <= B
x_max = min(A, C - 2) # x的上限:确保y = C - x >= 2
if x_min > x_max:
return None # 无解
min_abs = float('inf')
best_x, best_y = None, None
for x in range(x_min, x_max + 1):
y = C - x
if y < 2 or y > B:
continue # y不满足条件
current_abs = abs(A / x - B / y)
if current_abs < min_abs:
min_abs = current_abs
best_x, best_y = x, y
return (best_x, best_y) if best_x is not None else None
- 计算 x 的范围
x_min = max(2, C - B)
:确保 y=C−x≤B。x_max = min(A, C - 2)
:确保 y=C−x≥2。
- 遍历并验证解
- 遍历 x 从
x_min
到x_max
,计算对应的 y=C−x。 - 检查 y 是否在 [2,B] 内。
- 计算绝对值并更新最小值。
- 遍历 x 从
- 边界处理
- 若
x_min > x_max
,直接返回None
。 - 若遍历结束后未找到有效解,返回
None
。
- 若
针对极大 C 的优化
通过分析目标函数 $\left | \frac{A}{x}-\frac{B}{C-x} \right | $,找到理论最优解附近的候选点,避免完全遍历。
推导步骤
-
目标函数化简
忽略绝对值,定义函数:$f(x) = \frac{A}{x} - \frac{B}{C-x} $
寻找 f(x)=0 的解,即:
$\frac{A}{x} = \frac{B}{C-x} \Longrightarrow x = \frac{A·C}{A+B} $
记理论最优解为 $x_{0} = \frac{A·C}{A+B} $。
-
确定候选范围
实际解需满足 x∈[xmin,xmax]。只需检查 x0 附近的整数点(如 ⌊x0⌋−2 到 ⌊x0⌋+2),计算所有候选点的 ∣f(x)∣。 -
边界处理
若 x0 超出有效范围,则直接检查边界点 xmin 和 xmax。
代码
def optimized_min_abs_diff(A, B, C):
"""
针对极大 C 的优化算法,直接定位候选点附近求解。
"""
x_min = max(2, C - B)
x_max = min(A, C - 2)
if x_min > x_max:
return None
# 计算理论最优解 x0
x0 = (A * C) / (A + B)
candidates = []
# 生成候选点(覆盖 x0 附近的整数)
for dx in [-2, -1, 0, 1, 2]:
candidates.append(int(x0 + dx))
# 添加边界点
candidates.extend([x_min, x_max])
# 去重并过滤无效点
valid_x = set()
for x in candidates:
if x_min <= x <= x_max:
valid_x.add(x)
# 遍历候选点
min_abs = float('inf')
best_x, best_y = None, None
for x in valid_x:
y = C - x
if y < 2 or y > B:
continue
current_abs = abs(A / x - B / y)
if current_abs < min_abs:
min_abs = current_abs
best_x, best_y = x, y
return (best_x, best_y) if best_x is not None else None
# 示例测试
if __name__ == "__main__":
# 示例1: A=1000, B=2000, C=1e6 → 理论解 x0=333333.333...
print(optimized_min_abs_diff(1000, 2000, 10**6)) # 输出 (333333, 666667)
# 示例2: A=3, B=6, C=6 → 解为 (2,4)
print(optimized_min_abs_diff(3, 6, 6)) # 输出 (2, 4)
误区
最开始设定:男生每个房间的人数为 x,女生每个房间的人数为 y。
步骤一:方程变形与条件分析
那么有关系:
A x + B y = C \frac{A}{x} + \frac{B}{y} = C xA+yB=C
将其两边乘以 xy 得:
A y + B y = C x y Ay+By =Cxy Ay+By=Cxy
整理为:
( C x − A ) ( C y − B ) = A B (Cx−A)(Cy−B)=AB (Cx−A)(Cy−B)=AB
此时,C**x−A 和 C**y−B 必须为正整数,即:
$x > \frac{A}{C}, y > \frac{B}{C} $
步骤二:存在性条件
方程存在正整数解当且仅当 AB 能分解为两个正整数 p 和 q 的乘积,满足:
p ≡ − A ( m o d C ) , q ≡ − B ( m o d C ) p\equiv -A (\bmod C ), q\equiv -B (\bmod C ) p≡−A(modC),q≡−B(modC)
且对应的解为:
$x=\frac{p+A}{C}, y=\frac{q+B}{C} $
其中 x 和 y 均为正整数。
步骤三:求解步骤
- 分解因数对:列出 AB 的所有正因数对 (p,q),即 p⋅q=AB。
- 筛选同余条件:保留满足 p ≡ − A ( m o d C ) , q ≡ − B ( m o d C ) p\equiv -A (\bmod C ), q\equiv -B (\bmod C ) p≡−A(modC),q≡−B(modC)的因数对。
- 计算解:对每个符合条件的 (p,q),计算 x = p + A C , y = q + B C x=\frac{p+A}{C}, y=\frac{q+B}{C} x=Cp+A,y=Cq+B,并验证是否为正整数。
- 最小化:在所有解中,选择 ∣x−y∣ 最小的解。
代码
def find_min_abs_diff_solution(A, B, C):
"""
求解方程 A/x + B/y = C 的正整数解,并返回使得 |x - y| 最小的解 (x, y)
若无解则返回 None
"""
product = A * B
factor_pairs = []
# 生成所有正因数对 (p, q),满足 p * q = A*B
for i in range(1, int(product**0.5) + 1):
if product % i == 0:
factor_pairs.append((i, product // i))
if i != product // i: # 避免重复添加平方数情况
factor_pairs.append((product // i, i))
valid_solutions = []
required_p_mod = (-A) % C # p需要满足的同余条件
required_q_mod = (-B) % C # q需要满足的同余条件
for p, q in factor_pairs:
# 检查同余条件和整除性
if (p % C == required_p_mod) and (q % C == required_q_mod):
# 计算x和y,并验证是否为正整数
if (p + A) % C == 0 and (q + B) % C == 0:
x = (p + A) // C
y = (q + B) // C
if x > 0 and y > 0: # 确保x和y为正整数
valid_solutions.append((x, y))
if not valid_solutions:
return None # 无解
# 找出 |x - y| 最小的解
min_diff = float('inf')
best_solution = None
for x, y in valid_solutions:
current_diff = abs(x - y)
if current_diff < min_diff:
min_diff = current_diff
best_solution = (x, y)
elif current_diff == min_diff:
# 如果存在多个解差值相同,可在此处添加选择逻辑(如优先x+y最小)
pass
return best_solution
# 示例测试
if __name__ == "__main__":
# 示例1: A=2, B=3, C=1 → 解为 (5,5)
print(find_min_abs_diff_solution(2, 3, 1)) # 输出 (5,5)
# 示例2: A=3, B=4, C=2 → 解为 (3,4)
print(find_min_abs_diff_solution(3, 4, 2)) # 输出 (3,4)
# 示例3: A=1, B=1, C=3 → 无解
print(find_min_abs_diff_solution(1, 1, 3)) # 输出 None
该算法的时间复杂度主要由以下两个步骤决定:
- 生成所有因数对:时间复杂度为 O(√(A·B))
通过遍历从 1 到 √(A·B) 的所有整数,寻找 A·B 的因数对。每次循环需要常数时间,总循环次数为 √(A·B)。 - 筛选因数对并验证条件:时间复杂度为 O(d(A·B))
其中 d(A·B) 是 A·B 的因数总数。对于每个因数对 (p, q),检查同余条件和计算解需要常数时间。
综合时间复杂度:O(√(A·B) + O(d(A·B))
- √(A·B) 的主导性:
对于大多数情况,√(A·B) 远大于 d(A·B)(例如 A·B = 1e6 时,√(A·B)=1e3,而 d(A·B)=36)。因此,总时间复杂度通常由 O(√(A·B)) 主导。 - 极端情况下的表现:
如果 A·B 是平方数且因数较多(例如 A·B=2^30),d(A·B)=31,此时时间复杂度主要由 √(A·B)=2^15=32768 决定。
优化与局限性
- 适用性:
该算法在 A 和 B 较小时高效,但当 A·B 极大时(例如 A=1e18, B=1e18),√(A·B)=1e9 次循环会超出时间限制。 - 改进方向:
若问题允许,可结合质因数分解优化因数生成,或利用数学性质直接构造解,避免完全遍历。但对于通用情况,当前方法已是最优。
该文章有 DeepSeek 参与。
36)。因此,总时间复杂度通常由 O(√(A·B)) 主导。
- 极端情况下的表现:
如果 A·B 是平方数且因数较多(例如 A·B=2^30),d(A·B)=31,此时时间复杂度主要由 √(A·B)=2^15=32768 决定。
优化与局限性
- 适用性:
该算法在 A 和 B 较小时高效,但当 A·B 极大时(例如 A=1e18, B=1e18),√(A·B)=1e9 次循环会超出时间限制。 - 改进方向:
若问题允许,可结合质因数分解优化因数生成,或利用数学性质直接构造解,避免完全遍历。但对于通用情况,当前方法已是最优。
该文章有 DeepSeek 参与。