1 条题解
-
0
B1. Canteen (Easy Version) 详细题解
题目描述
这是该问题的简单版本,其中 ,即不能对序列 做任何修改。
给定两个长度为 的整数序列 和 ,保证 。
每轮操作分为三步:
- 消耗阶段:对于每个 ,令 ,然后:
- 移位阶段:将 数组循环右移一位,即:
- 重复上述过程直到所有
求最少需要的轮数。
核心思想
1. 问题转化
对于每个位置 上的初始 ,它会在若干轮后被消耗完。由于 每轮向右循环移位,第 个位置的 实际上会依次与 进行匹配(下标模 )。
定义 为将 减少到 所需的最小轮数,那么答案就是:
2. 循环展开
对于循环问题,将数组复制一份接在后面:
这样只需要考虑长度为 的链, 就是找到最小的 使得 可以被 完全消耗。
3. 括号匹配模型
构造一个括号序列:将每个 视为 个左括号
$$s = \underbrace{(\cdots(}_{a_0\text{个}} \underbrace{)\cdots)}_{b_0\text{个}} \underbrace{(\cdots(}_{a_1\text{个}} \underbrace{)\cdots)}_{b_1\text{个}} \cdots \underbrace{(\cdots(}_{a_{2n-1}\text{个}} \underbrace{)\cdots)}_{b_{2n-1}\text{个}} $$(,每个 视为 个右括号),按顺序拼接:原问题的操作对应括号匹配过程:
- 第一步( 归零)对应将当前位置的左右括号进行匹配消除
- 第二步( 循环右移)对应未匹配的左括号继续向右寻找右括号
因此, 第一次被归零的时刻,取决于 对应的左括号与哪个 的右括号完成匹配。
4. 前缀和与单调栈
定义差分:
计算前缀和:
$$c_i = \sum_{j=0}^{i} (a_j - b_j) = \sum_{j=0}^{i} d_j,\quad 0 \le i < 2n $$括号匹配的关键观察:
对于位置 , 对应的左括号找到匹配右括号的位置 是满足 且 的最小下标。原因:
- 表示到位置 为止未匹配的左括号数量(左括号为正,右括号为负)
- 当 的值下降到不高于 时,意味着 之后出现的左括号已经全部被匹配, 处的左括号也得到了匹配
- 就是 被完全消耗时对应的 的位置
于是:
5. 单调栈求
需要找到每个 右边第一个满足 的位置 ,使用单调递增栈(按 值)从右向左扫描:
- 初始化空栈,答案
- 从 向下遍历到 :
- 当栈非空且 时,弹出栈顶(维护单调递增)
- 此时栈顶就是右边第一个 值不大于 的位置,即 (若栈空则 )
- 如果 ,则
- 将 入栈
算法步骤
- 读入 ( 不影响)
- 读入数组 和
- 构造长度为 的差分数组并计算前缀和
- 使用单调栈从右向左扫描,计算每个 对应的 ,更新答案
- 输出答案
时间复杂度
- 每个测试用例
- 所有测试用例的 总和不超过 ,完全可行
代码实现(C++)
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int MAXN = 400009; ll n, k; ll a[MAXN], stk[MAXN]; inline ll read() { ll s = 0, w = 1; char ch = getchar(); while (ch > '9' || ch < '0') { if (ch == '-') w = -1; ch = getchar(); } while (ch <= '9' && ch >= '0') { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); } return s * w; } int main() { ll t = read(); while (t--) { n = read(), k = read(); // k = 0 in this version // 读入 a 数组 for (ll i = 1; i <= n; i++) { a[i] = read(); } // 计算 a[i] - b[i],并复制一份 for (ll i = 1; i <= n; i++) { ll b = read(); a[i] = a[i + n] = a[i] - b; } // 计算前缀和 for (ll i = 1; i <= 2 * n; i++) { a[i] += a[i - 1]; } // 单调栈从右向左扫描 ll tp = 0, ans = 0; for (ll i = 2 * n; i >= 1; i--) { // 维护单调递增栈(按 a[栈顶] 的值) while (tp && a[stk[tp]] > a[i]) { tp--; } // 对于原始范围内的位置,更新答案 if (i <= n) { ans = max(ans, stk[tp] - i); } // 当前下标入栈 stk[++tp] = i; } printf("%lld\n", ans); } return 0; }
示例验证
示例输入
4 3 0 1 1 4 5 1 4 4 0 1 2 3 4 4 3 2 1 4 0 2 1 1 2 1 2 2 1 8 0 1 2 3 4 5 6 7 8 8 7 6 5 4 3 2 1示例输出
1 4 4 8第一个测试用例详解
计算 ,复制得
前缀和 :
$$c_1=-4,\ c_2=-4,\ c_3=-4,\ c_4=-8,\ c_5=-8,\ c_6=-8 $$从右向左单调栈扫描:
- :栈空,入栈 → 栈 , 不更新
- :,不弹出,入栈 → 栈
- :,不弹出,入栈 → 栈
- :,弹出 4;,弹出 5;,弹出 6;栈空, 不存在,取 ,
- :,栈空,
- :,栈空,
但实际模拟可知答案应为 1,说明上述计算有误。注意:题目保证 ,但前缀和 可能在末尾大于起点,需要特殊处理。实际标程中,由于栈中始终有元素(复制保证了末尾有更小值),栈不会为空。正确的单调栈维护的是严格大于才弹出,保证栈底是全局最小值。
重新计算正确过程:
- :栈
- :,不弹出,栈
- :,不弹出,栈
- :,弹出 4,栈 ;,弹出 5,栈 ;?实际 ,继续弹出 6,栈空。此时 栈空,取 ,,但答案应为 1,说明此处 不在 范围内(?注意下标从 1 开始, 对应原始位置 2,实际上 是 的边界,应计入)。需要仔细核对。
实际上,对于 (原始位置 1):,右边第一个 且 的是 (),。对于 (原始位置 0):,右边第一个 的是 ,。最大值确实为 1。
总结
本题的核心是将原问题映射到括号匹配模型,通过构造括号序列并利用前缀和与单调栈,在 时间内求出每个 被消耗所需的轮数,取最大值即为答案。这种转化思想在处理循环序列问题时非常有用。
- 1
信息
- ID
- 6267
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者