P14635 NOIP 2025 T1 糖果店 假的贪心做法错误原因的分析
糖果店购买问题的贪心算法分析
摘要
本文针对 NOIP 2025 第一题糖果店(candy)问题,分析了一种基于优先队列的贪心算法的正确性。通过数学建模和反例构造,我们证明了该算法在特定条件下会输出且仅输出比最优解少
1. 问题描述
1.1 问题定义
给定
目标是在预算
输入:
输出:最大糖果数量
1.2 标准最优解法
最优解的结构为购买
最优解为:
2. 贪心算法描述
该算法是待证伪正确性的算法。其核心思想是每次选择当前性价比最高的操作(购买一颗糖果或一对糖果)。性价比通过平均成本衡量:购买一颗糖果的平均成本为
2.1 算法伪代码
2.2 算法思想
算法维护一个优先队列
- 单颗购买:成本为
x_i (第一次购买)或y_i (后续购买),平均成本即成本本身。 - 成对购买:平均成本为
\frac{x_i + y_i}{2} 。
算法每次从队列中取出平均成本最小的操作执行,并更新队列。这种贪心策略旨在局部最大化糖果数量,但无法保证全局最优。
3. 错误分析
3.1 错误模式
设标准最优解为
贪心算法输出
性质 P:
3.2 错误证明
当性质 P 成立时:
- 由于单颗购买的平均成本低于成对购买的平均成本,贪心算法优先购买该单糖。
- 但由于
c > d_{t^*} ,购买该单糖后预算不足维持k^* 对购买,因此k_g = k^* - 1 。 - 同时,贪心算法购买了
t_g = t^* + 1 颗单糖。 - 因此,总糖果数为:
f_g = (t^* + 1) + 2(k^* - 1) = t^* + 2k^* - 1 = f^* - 1.
3.3 反例验证
考虑反例:
- 计算
C = \min(11+2, 5+13) = \min(13, 18) = 13 。 - 排序单糖成本:
x_{(1)} = 5 ,x_{(2)} = 11 。 - 枚举
t : -
- 最优解
f^* = 2 。
性质 P 检查:
-
-
- 贪心算法输出
1 = 2 - 1 ,符合预测。
4. 实验验证
4.1 随机测试结果
在
- 正确率:
99.68\% - 错误率:
0.32\% - 所有错误案例输出均为
f^* - 1
4.2 错误率分析
错误率低的原因在于性质 P 的条件苛刻:
- 需要存在单糖成本
c 满足c > d_{t^*} 且\frac{c}{1} < \frac{C}{2} 。 - 在均匀随机数据中,
d_{t^*} 通常较大,C 通常较小,性质 P 难以满足。
5. 结论
本文分析了糖果店购买问题的贪心算法,证明了其错误模式为输出
6. 附录
标准解法代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
int n; ll m;
cin >> n >> m;
vector<ll> x(n);
ll min_pair = 1e18;
for (int i = 0; i < n; i++) {
ll xi, yi; cin >> xi >> yi;
x[i] = xi;
min_pair = min(min_pair, xi + yi);
}
sort(x.begin(), x.end());
vector<ll> prefix(n + 1, 0);
for (int i = 0; i < n; i++) prefix[i + 1] = prefix[i] + x[i];
ll ans = 0;
for (int t = 0; t <= n; t++) {
if (prefix[t] > m) break;
ll k = (m - prefix[t]) / min_pair;
ans = max(ans, 2 * k + t);
}
cout << ans << endl;
return 0;
}
该解法时间复杂度
考场代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100004;
int n;
long long m;
int main()
{
// freopen("candy7.in", "r", stdin);
// 这个代码和场上代码一样地在第六个点输出 82 instead of 83
cin.tie(0); ios::sync_with_stdio(0);
cin>>n>>m;
priority_queue<pair<long long, long long>, vector<pair<long long, long long> >, greater<pair<long long, long long> >> pq;
for(int i=1;i<=n;i++)
{
long long x, y;
cin>>x>>y;
pq.push({x*2, y*2});
pq.push({x+y, -1});
}
long long ans = 0;
while(!pq.empty())
{
auto u = pq.top();
// printf("m=%lld, u=(%lld, %lld)\n", m, u.first, u.second);
if(u.second == -1)
{
if(m - u.first < 0)
{
pq.pop();
continue;
}
long long cost = m/u.first;
// printf("取出 %d 个 x_i+y_i=cost=%lld\n", cost, u.first*(m/u.first));
m -= u.first*cost;
ans += cost * 2;
continue;
}
if(m - u.first/2 < 0) break;
m -= u.first/2;
ans++;
pair<long long, long long> cur = {u.second, u.first};
pq.pop();
pq.push(cur);
}
cout<<ans<<'\n';
return 0;
}