题解:洛谷 AT_abc443_b Setsubun
【题目来源】
洛谷:AT_abc443_b [ABC443B] Setsubun - 洛谷(luogu.com.cn)
【题目描述】
在一年一度的 Setsubun 节上,人们要吃与自己年龄相同数量的豆子。高桥君在其他任何时候都不吃豆子。
他在今年的 Setsubun 节时 \(N\) 岁(\(0\) 年后)。
从这一年开始(包括这一年),他最早会在多少年后吃掉 \(K\) 或更多粒豆子?
他是不死之身,所以从今年开始,他最终一定会吃掉 \(K\) 或更多粒豆子。
【输入】
输入内容按如下格式给出:
\(N\) \(K\)
【输出】
输出一行表示答案。
【输入样例】
4 43
【输出样例】
6
【核心思想】
-
问题分析:高桥从 \(N\) 岁开始在每年 Setsubun 节吃与自己年龄相同数量的豆子。求最早多少年后累计吃掉 \(K\) 或更多粒豆子。设经过 \(t\) 年,则第 \(i\) 年(\(0 \leq i \leq t\))吃 \(N + i\) 粒豆子,累计吃的豆子数为 \(\sum_{i=0}^{t}(N + i) = (t+1)N + \frac{t(t+1)}{2} = (t+1)(N + \frac{t}{2})\)。这是一个整数二分查找问题,核心在于找到满足累计豆子数 \(\geq K\) 的最小 \(t\)。
-
算法选择:
- 整数二分:在答案范围 \([0, 10^8]\) 内二分查找最小的 \(t\),使得累计豆子数 \(\geq K\)
-
关键步骤:
- 读入数据:\(N\)、\(K\)
- 二分查找(\(l = 0, r = 10^8\)):
- \(mid = (l + r) / 2\)
- 计算累计豆子数:\(ans = mid \times (mid + 1) / 2 + N \times (mid + 1)\)
- 若 \(ans \geq K\):\(r = mid\)
- 否则:\(l = mid + 1\)
- 输出结果:\(l\)
-
时间/空间复杂度:
- 时间复杂度:\(O(\log 10^8) \approx O(27)\),二分查找的迭代次数
- 空间复杂度:\(O(1)\)
-
整数二分与等差数列求和的核心思想:
- 累计公式的推导:经过 \(t\) 年,共 \(t+1\) 个 Setsubun 节(包括今年)。年龄序列为 \(N, N+1, \ldots, N+t\),是首项 \(N\)、末项 \(N+t\)、项数 \(t+1\) 的等差数列。和 = \(\frac{(t+1)(2N + t)}{2} = (t+1)N + \frac{t(t+1)}{2}\),与代码中的
mid*(mid+1)/2 + n*(mid+1)一致(其中 \(mid = t\)) - 单调性保证:累计豆子数随 \(t\) 严格递增,满足二分查找的单调性条件
- 整数二分的边界处理:
while (l < r)配合r = mid和l = mid + 1,最终 \(l = r\) 即为最小满足条件的 \(t\) - 不死之身的保证:题目保证最终一定能达到 \(K\),因此二分范围只需足够大即可,无需处理无解情况
- 适用于等差数列求和、单调函数求根、整数二分查找类问题
- 累计公式的推导:经过 \(t\) 年,共 \(t+1\) 个 Setsubun 节(包括今年)。年龄序列为 \(N, N+1, \ldots, N+t\),是首项 \(N\)、末项 \(N+t\)、项数 \(t+1\) 的等差数列。和 = \(\frac{(t+1)(2N + t)}{2} = (t+1)N + \frac{t(t+1)}{2}\),与代码中的
【算法标签】
入门 #整数二分
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long // 将int定义为long long类型,避免大数溢出
int n, k; // n: 初始值,k: 目标值
// 检查函数:判断mid是否满足条件
bool check(int mid)
{
// 计算公式:mid*(mid+1)/2 + n*(mid+1)
// 第一部分:1到mid的等差数列和
// 第二部分:n重复(mid+1)次的和
int ans = 0;
ans = mid * (mid + 1) / 2 + n * (mid + 1);
return ans >= k; // 判断结果是否达到目标k
}
signed main() // 使用signed main()替代int main(),因为int被重定义为long long
{
cin >> n >> k; // 读入初始值n和目标值k
// 二分查找范围
int l = 0, r = 100000000;
// 二分查找
while (l < r)
{
int mid = (l + r) / 2;
if (check(mid)) // 如果mid满足条件
{
r = mid; // 缩小右边界
}
else
{
l = mid + 1; // 增大左边界
}
}
// 输出最小满足条件的值
cout << l << endl;
return 0;
}
【运行结果】
4 43
6
浙公网安备 33010602011771号