1 条题解
-
0
自动搬运
来自洛谷,原作者为

GXZJQ
AFO||十年竞赛一场空 不开 long long 见祖宗搬运于
2025-08-24 22:57:53,当前版本为作者最后更新于2024-04-30 14:36:27,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
P10385 [蓝桥杯 2024 省 B] 拔河 题解
-
更新于 年 月 日:修改了第二份代码中的小错误,感谢 MyNameIsikun 的指正和 hack 数据。
-
更新于 年 月 日:修改了题目链接的指向,感谢 wzj0829 的提醒。
题目大意
给定一个长度为 的序列,求出两个子序列 和 中数字和的差值的最小值,保证 。
题目分析
思路一:暴力
使用循环,暴力枚举区间的左右端点并进行求和,最后比较差值。由于枚举端点的时间复杂度为 ,求和的时间复杂度为 ,所以最终时间复杂度为 ,无法通过本题。
思路二:暴力+前缀和
同思路一,暴力枚举区间的左右端点并进行求和,最后比较差值。但我们使用前缀和维护序列和的时候可以优化掉暴力求和的 ,所以最终时间复杂度为 ,无法通过本题。
思路三:双指针
-
枚举 ,定义双指针 ,分别从 和 ,分别从左边和右边扫描整个序列;
-
枚举 ,定义双指针 ,向中间扫描整个序列。
使用双指针的总体复杂度为 ,理论上可以通过,但还不够优秀。
思路四:枚举单个区间
双指针算法仅仅是理论上可以通过,但可能会出现一些小的情况造成超时。这时我们就要用一种时间复杂度更为优秀的算法。
我们尝试只用 枚举第一个区间 ,然后回归题目本身,它要求数字和差值尽可能小,所以要在右区间中找到和左区间的和最相近的值,不妨使用一个
multiset进行维护,记其为 。开始的时候,我们把所有可能的右区间的和都加入 中去,接下来用 枚举左区间,当 时,在 中删除以 为左端点的区间和。
细节和优化
-
用前缀和维护整个序列的数字和;
-
使用
lower_bound二分查找第一个大于等于 的位置; -
注意
multiset的维护,不可以直接使用erase,而是要对其传入一个值为 的元素的迭代器,有指向性地删除。
参考代码
双指针
#include<bits/stdc++.h> using namespace std; const int maxn = 1e3 + 10; int n, a[maxn]; int ans = INT_MAX; int minnum(int a, long long b) { return a > b ? b : a; } int main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin >> n; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i <= n; i++) for (int j = i + 1; j <= n; j++) { int l = i - 1, r = j + 1; long long suml = a[i], sumr = a[j]; ans = minnum(ans, abs(suml - sumr)); while (l >= 1 && r <= n) { if (suml > sumr) sumr += a[r++]; else suml += a[l--]; ans = minnum(ans, abs(suml - sumr)); } while (l >= 1) { suml += a[l--]; ans = minnum(ans, abs(suml - sumr)); } while (r <= n) { sumr += a[r++]; ans = minnum(ans, abs(suml - sumr)); } } cout << ans << endl; return 0; }枚举单个区间
#include<bits/stdc++.h> using namespace std; const int maxn = 1e3 + 10; int n; long long a[maxn], ans = 1e9; multiset<long long> S; long long minnum(long long a, long long b) { return a > b ? b : a; } int main() { ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin >> n; for (int i = 1; i <= n; i++) cin >> a[i], a[i] = a[i - 1] + a[i];//前缀和维护 for (int i = 1; i <= n; i++) for (int j = i; j <= n; j++) S.insert(a[j] - a[i - 1]);//全部插入 for (int i = 1; i < n; i++) { for (int j = i; j <= n; j++) { auto p = S.find(a[j] - a[i - 1]); S.erase(p);//删除 } for (int j = 1; j <= i; j++) { auto k = a[i] - a[j - 1]; auto p = S.lower_bound(k); if (p != S.end()) ans = minnum(ans, abs(*p - k)); if (p != S.begin()) p --, ans = minnum(ans, abs(*p - k)); } } cout << ans; return 0; } -
- 1
信息
- ID
- 10207
- 时间
- 1000ms
- 内存
- 512MiB
- 难度
- 4
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者