给大家分享我 4 年元宵节原创的切小朋友分苹果算法题

2026-04-29 09:582阅读0评论SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐
问题描述:

看到最近比较火的分苹果题目,想到了之前出的题目,你说这巧不巧

一句话题意:n 个苹果 m 个小朋友,可以一刀切掉一个小朋友 / 一口吃一个苹果,求最少几次操作平分。

加载中... (学校OJ)
image2526×1674 355 KB
image1820×1536 181 KB

题解…已经忘了,不过可以试一试问 AI,有一个根号的做法,也有一个随机数据下期望 log(最坏退化到根号)的做法

补充一下GPT题解,仅供参考,还没时间验证,感兴趣的可以自己看看,不过这题也没啥意思:

1. 化式

最终保留 d 个小朋友,必须 1\le d\le \min(n,m)。

苹果数要变成 d 的倍数,最少吃掉:

n\bmod d

小朋友要砍掉:

m-d

所以单次询问答案是:

\operatorname{ans}(n,m)=\min_{1\le d\le \min(n,m)} \left(n\bmod d+m-d\right)

如果 m>n,显然最优是保留 d=n 个小朋友,不吃苹果:

\operatorname{ans}=m-n

下面只考虑 n\ge m。


2. 关键引理:只需考虑 d>m/2

设某个方案保留 d\le m/2 个小朋友,每人分 k 个苹果,即保留苹果数是 kd。

因为 2d\le m,考虑改成保留 2d 个小朋友。

如果 k 是偶数,令每人分 k/2 个苹果,则保留苹果数仍是 kd,但小朋友更多,操作数更少。

如果 k 是奇数且 k\ge 3,令每人分 (k-1)/2 个苹果,则保留苹果数变成 (k-1)d,少了 d 个苹果,但多保留了 d 个小朋友,总操作数不变。

如果 k=1,因为 n\ge m\ge 2d,保留 2d 个小朋友、每人 1 个苹果也是合法的,而且更优。

所以任何 d\le m/2 都可以替换成 2d,不变差。因此最优 d 可以只在:

\left\lfloor \frac m2\right\rfloor+1\le d\le m

里找。


3. 在上半段按商分块

令:

L=\left\lfloor \frac m2\right\rfloor+1

只枚举 d\in[L,m]。

对固定的商:

q=\left\lfloor \frac nd\right\rfloor

所有满足这个商的 d 构成区间:

\left[\left\lfloor\frac n{q+1}\right\rfloor+1,\left\lfloor\frac nq\right\rfloor\right]

在同一个 q 内:

n\bmod d+m-d=n-qd+m-d=n+m-(q+1)d

显然 d 越大越优,所以每个商只需要检查该区间里的最大 d。

在 d\in[L,m] 中,商的范围是:

q_{\min}=\left\lfloor\frac nm\right\rfloor,\qquad q_{\max}=\left\lfloor\frac nL\right\rfloor

由于 L>m/2,所以:

q_{\max}<\frac{2n}{m}

也就是说商的数量大约是 O(n/m)。

另一方面,如果 m 很小,直接枚举 d\in[L,m] 只要 O(m)。

所以单次复杂度:

O\left(\min\left(m,\frac nm\right)\right)

随机 n,m\in[1,V] 时,期望是 O(\log V)。


4. C++17 代码

#include <bits/stdc++.h> using namespace std; using int64 = long long; using i128 = __int128_t; int64 solve_one(int64 n, int64 m) { // 苹果比小朋友少:保留 n 个小朋友,不吃苹果 if (m > n) return m - n; // n >= m,只需要考虑 d in [L, m] int64 L = m / 2 + 1; int64 d_cnt = m - L + 1; int64 q_min = n / m; int64 q_max = n / L; int64 q_cnt = q_max - q_min + 1; i128 best = (i128)4e18; // 哪边少枚举哪边 if (d_cnt <= q_cnt) { // 直接枚举 d for (int64 d = L; d <= m; d++) { i128 cur = (i128)(n % d) + (m - d); if (cur < best) best = cur; } } else { // 按 q = floor(n / d) 分块 for (int64 q = q_min; q <= q_max; q++) { int64 left = n / (q + 1) + 1; int64 right = n / q; left = max(left, L); right = min(right, m); if (left <= right) { int64 d = right; // 同一个 q 内,d 越大越优 i128 cur = (i128)(n % d) + (m - d); if (cur < best) best = cur; } } } return (int64)best; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int Q; cin >> Q; long long xr = 0; while (Q--) { int64 n, m; cin >> n >> m; int64 ans = solve_one(n, m); xr ^= ans; } cout << xr << '\n'; return 0; } 网友解答:


--【壹】--:

看到最近比较火的分苹果题目,想到了之前出的题目,你说这巧不巧

一句话题意:n 个苹果 m 个小朋友,可以一刀切掉一个小朋友 / 一口吃一个苹果,求最少几次操作平分。

加载中... (学校OJ)
image2526×1674 355 KB
image1820×1536 181 KB

题解…已经忘了,不过可以试一试问 AI,有一个根号的做法,也有一个随机数据下期望 log(最坏退化到根号)的做法

补充一下GPT题解,仅供参考,还没时间验证,感兴趣的可以自己看看,不过这题也没啥意思:

1. 化式

最终保留 d 个小朋友,必须 1\le d\le \min(n,m)。

苹果数要变成 d 的倍数,最少吃掉:

n\bmod d

小朋友要砍掉:

m-d

所以单次询问答案是:

\operatorname{ans}(n,m)=\min_{1\le d\le \min(n,m)} \left(n\bmod d+m-d\right)

如果 m>n,显然最优是保留 d=n 个小朋友,不吃苹果:

\operatorname{ans}=m-n

下面只考虑 n\ge m。


2. 关键引理:只需考虑 d>m/2

设某个方案保留 d\le m/2 个小朋友,每人分 k 个苹果,即保留苹果数是 kd。

因为 2d\le m,考虑改成保留 2d 个小朋友。

如果 k 是偶数,令每人分 k/2 个苹果,则保留苹果数仍是 kd,但小朋友更多,操作数更少。

如果 k 是奇数且 k\ge 3,令每人分 (k-1)/2 个苹果,则保留苹果数变成 (k-1)d,少了 d 个苹果,但多保留了 d 个小朋友,总操作数不变。

如果 k=1,因为 n\ge m\ge 2d,保留 2d 个小朋友、每人 1 个苹果也是合法的,而且更优。

所以任何 d\le m/2 都可以替换成 2d,不变差。因此最优 d 可以只在:

\left\lfloor \frac m2\right\rfloor+1\le d\le m

里找。


3. 在上半段按商分块

令:

L=\left\lfloor \frac m2\right\rfloor+1

只枚举 d\in[L,m]。

对固定的商:

q=\left\lfloor \frac nd\right\rfloor

所有满足这个商的 d 构成区间:

\left[\left\lfloor\frac n{q+1}\right\rfloor+1,\left\lfloor\frac nq\right\rfloor\right]

在同一个 q 内:

n\bmod d+m-d=n-qd+m-d=n+m-(q+1)d

显然 d 越大越优,所以每个商只需要检查该区间里的最大 d。

在 d\in[L,m] 中,商的范围是:

q_{\min}=\left\lfloor\frac nm\right\rfloor,\qquad q_{\max}=\left\lfloor\frac nL\right\rfloor

由于 L>m/2,所以:

q_{\max}<\frac{2n}{m}

也就是说商的数量大约是 O(n/m)。

另一方面,如果 m 很小,直接枚举 d\in[L,m] 只要 O(m)。

所以单次复杂度:

O\left(\min\left(m,\frac nm\right)\right)

随机 n,m\in[1,V] 时,期望是 O(\log V)。


4. C++17 代码

#include <bits/stdc++.h> using namespace std; using int64 = long long; using i128 = __int128_t; int64 solve_one(int64 n, int64 m) { // 苹果比小朋友少:保留 n 个小朋友,不吃苹果 if (m > n) return m - n; // n >= m,只需要考虑 d in [L, m] int64 L = m / 2 + 1; int64 d_cnt = m - L + 1; int64 q_min = n / m; int64 q_max = n / L; int64 q_cnt = q_max - q_min + 1; i128 best = (i128)4e18; // 哪边少枚举哪边 if (d_cnt <= q_cnt) { // 直接枚举 d for (int64 d = L; d <= m; d++) { i128 cur = (i128)(n % d) + (m - d); if (cur < best) best = cur; } } else { // 按 q = floor(n / d) 分块 for (int64 q = q_min; q <= q_max; q++) { int64 left = n / (q + 1) + 1; int64 right = n / q; left = max(left, L); right = min(right, m); if (left <= right) { int64 d = right; // 同一个 q 内,d 越大越优 i128 cur = (i128)(n % d) + (m - d); if (cur < best) best = cur; } } } return (int64)best; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int Q; cin >> Q; long long xr = 0; while (Q--) { int64 n, m; cin >> n >> m; int64 ans = solve_one(n, m); xr ^= ans; } cout << xr << '\n'; return 0; }

标签:算法
问题描述:

看到最近比较火的分苹果题目,想到了之前出的题目,你说这巧不巧

一句话题意:n 个苹果 m 个小朋友,可以一刀切掉一个小朋友 / 一口吃一个苹果,求最少几次操作平分。

加载中... (学校OJ)
image2526×1674 355 KB
image1820×1536 181 KB

题解…已经忘了,不过可以试一试问 AI,有一个根号的做法,也有一个随机数据下期望 log(最坏退化到根号)的做法

补充一下GPT题解,仅供参考,还没时间验证,感兴趣的可以自己看看,不过这题也没啥意思:

1. 化式

最终保留 d 个小朋友,必须 1\le d\le \min(n,m)。

苹果数要变成 d 的倍数,最少吃掉:

n\bmod d

小朋友要砍掉:

m-d

所以单次询问答案是:

\operatorname{ans}(n,m)=\min_{1\le d\le \min(n,m)} \left(n\bmod d+m-d\right)

如果 m>n,显然最优是保留 d=n 个小朋友,不吃苹果:

\operatorname{ans}=m-n

下面只考虑 n\ge m。


2. 关键引理:只需考虑 d>m/2

设某个方案保留 d\le m/2 个小朋友,每人分 k 个苹果,即保留苹果数是 kd。

因为 2d\le m,考虑改成保留 2d 个小朋友。

如果 k 是偶数,令每人分 k/2 个苹果,则保留苹果数仍是 kd,但小朋友更多,操作数更少。

如果 k 是奇数且 k\ge 3,令每人分 (k-1)/2 个苹果,则保留苹果数变成 (k-1)d,少了 d 个苹果,但多保留了 d 个小朋友,总操作数不变。

如果 k=1,因为 n\ge m\ge 2d,保留 2d 个小朋友、每人 1 个苹果也是合法的,而且更优。

所以任何 d\le m/2 都可以替换成 2d,不变差。因此最优 d 可以只在:

\left\lfloor \frac m2\right\rfloor+1\le d\le m

里找。


3. 在上半段按商分块

令:

L=\left\lfloor \frac m2\right\rfloor+1

只枚举 d\in[L,m]。

对固定的商:

q=\left\lfloor \frac nd\right\rfloor

所有满足这个商的 d 构成区间:

\left[\left\lfloor\frac n{q+1}\right\rfloor+1,\left\lfloor\frac nq\right\rfloor\right]

在同一个 q 内:

n\bmod d+m-d=n-qd+m-d=n+m-(q+1)d

显然 d 越大越优,所以每个商只需要检查该区间里的最大 d。

在 d\in[L,m] 中,商的范围是:

q_{\min}=\left\lfloor\frac nm\right\rfloor,\qquad q_{\max}=\left\lfloor\frac nL\right\rfloor

由于 L>m/2,所以:

q_{\max}<\frac{2n}{m}

也就是说商的数量大约是 O(n/m)。

另一方面,如果 m 很小,直接枚举 d\in[L,m] 只要 O(m)。

所以单次复杂度:

O\left(\min\left(m,\frac nm\right)\right)

随机 n,m\in[1,V] 时,期望是 O(\log V)。


4. C++17 代码

#include <bits/stdc++.h> using namespace std; using int64 = long long; using i128 = __int128_t; int64 solve_one(int64 n, int64 m) { // 苹果比小朋友少:保留 n 个小朋友,不吃苹果 if (m > n) return m - n; // n >= m,只需要考虑 d in [L, m] int64 L = m / 2 + 1; int64 d_cnt = m - L + 1; int64 q_min = n / m; int64 q_max = n / L; int64 q_cnt = q_max - q_min + 1; i128 best = (i128)4e18; // 哪边少枚举哪边 if (d_cnt <= q_cnt) { // 直接枚举 d for (int64 d = L; d <= m; d++) { i128 cur = (i128)(n % d) + (m - d); if (cur < best) best = cur; } } else { // 按 q = floor(n / d) 分块 for (int64 q = q_min; q <= q_max; q++) { int64 left = n / (q + 1) + 1; int64 right = n / q; left = max(left, L); right = min(right, m); if (left <= right) { int64 d = right; // 同一个 q 内,d 越大越优 i128 cur = (i128)(n % d) + (m - d); if (cur < best) best = cur; } } } return (int64)best; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int Q; cin >> Q; long long xr = 0; while (Q--) { int64 n, m; cin >> n >> m; int64 ans = solve_one(n, m); xr ^= ans; } cout << xr << '\n'; return 0; } 网友解答:


--【壹】--:

看到最近比较火的分苹果题目,想到了之前出的题目,你说这巧不巧

一句话题意:n 个苹果 m 个小朋友,可以一刀切掉一个小朋友 / 一口吃一个苹果,求最少几次操作平分。

加载中... (学校OJ)
image2526×1674 355 KB
image1820×1536 181 KB

题解…已经忘了,不过可以试一试问 AI,有一个根号的做法,也有一个随机数据下期望 log(最坏退化到根号)的做法

补充一下GPT题解,仅供参考,还没时间验证,感兴趣的可以自己看看,不过这题也没啥意思:

1. 化式

最终保留 d 个小朋友,必须 1\le d\le \min(n,m)。

苹果数要变成 d 的倍数,最少吃掉:

n\bmod d

小朋友要砍掉:

m-d

所以单次询问答案是:

\operatorname{ans}(n,m)=\min_{1\le d\le \min(n,m)} \left(n\bmod d+m-d\right)

如果 m>n,显然最优是保留 d=n 个小朋友,不吃苹果:

\operatorname{ans}=m-n

下面只考虑 n\ge m。


2. 关键引理:只需考虑 d>m/2

设某个方案保留 d\le m/2 个小朋友,每人分 k 个苹果,即保留苹果数是 kd。

因为 2d\le m,考虑改成保留 2d 个小朋友。

如果 k 是偶数,令每人分 k/2 个苹果,则保留苹果数仍是 kd,但小朋友更多,操作数更少。

如果 k 是奇数且 k\ge 3,令每人分 (k-1)/2 个苹果,则保留苹果数变成 (k-1)d,少了 d 个苹果,但多保留了 d 个小朋友,总操作数不变。

如果 k=1,因为 n\ge m\ge 2d,保留 2d 个小朋友、每人 1 个苹果也是合法的,而且更优。

所以任何 d\le m/2 都可以替换成 2d,不变差。因此最优 d 可以只在:

\left\lfloor \frac m2\right\rfloor+1\le d\le m

里找。


3. 在上半段按商分块

令:

L=\left\lfloor \frac m2\right\rfloor+1

只枚举 d\in[L,m]。

对固定的商:

q=\left\lfloor \frac nd\right\rfloor

所有满足这个商的 d 构成区间:

\left[\left\lfloor\frac n{q+1}\right\rfloor+1,\left\lfloor\frac nq\right\rfloor\right]

在同一个 q 内:

n\bmod d+m-d=n-qd+m-d=n+m-(q+1)d

显然 d 越大越优,所以每个商只需要检查该区间里的最大 d。

在 d\in[L,m] 中,商的范围是:

q_{\min}=\left\lfloor\frac nm\right\rfloor,\qquad q_{\max}=\left\lfloor\frac nL\right\rfloor

由于 L>m/2,所以:

q_{\max}<\frac{2n}{m}

也就是说商的数量大约是 O(n/m)。

另一方面,如果 m 很小,直接枚举 d\in[L,m] 只要 O(m)。

所以单次复杂度:

O\left(\min\left(m,\frac nm\right)\right)

随机 n,m\in[1,V] 时,期望是 O(\log V)。


4. C++17 代码

#include <bits/stdc++.h> using namespace std; using int64 = long long; using i128 = __int128_t; int64 solve_one(int64 n, int64 m) { // 苹果比小朋友少:保留 n 个小朋友,不吃苹果 if (m > n) return m - n; // n >= m,只需要考虑 d in [L, m] int64 L = m / 2 + 1; int64 d_cnt = m - L + 1; int64 q_min = n / m; int64 q_max = n / L; int64 q_cnt = q_max - q_min + 1; i128 best = (i128)4e18; // 哪边少枚举哪边 if (d_cnt <= q_cnt) { // 直接枚举 d for (int64 d = L; d <= m; d++) { i128 cur = (i128)(n % d) + (m - d); if (cur < best) best = cur; } } else { // 按 q = floor(n / d) 分块 for (int64 q = q_min; q <= q_max; q++) { int64 left = n / (q + 1) + 1; int64 right = n / q; left = max(left, L); right = min(right, m); if (left <= right) { int64 d = right; // 同一个 q 内,d 越大越优 i128 cur = (i128)(n % d) + (m - d); if (cur < best) best = cur; } } } return (int64)best; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int Q; cin >> Q; long long xr = 0; while (Q--) { int64 n, m; cin >> n >> m; int64 ans = solve_one(n, m); xr ^= ans; } cout << xr << '\n'; return 0; }

标签:算法