只在相邻 RL 汇合点统计溢出,把两侧供奶链贡献扣去 min(链和, M)。
OJ: usaco
题目 ID: 1396
难度:普及/提高-
标签:模拟贡献统计环形结构思维usaco
日期: 2026-07-11 15:57
题意
有
每一分钟同时发生:
- 如果一头奶牛桶里至少有
升牛奶,她会按 L或R向相邻奶牛传出升。 - 如果某个桶收到牛奶后超过容量,超过的部分会损失。
求
思路
先看一个可以直接按题意验证的小数据模拟:
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-11 15:57
* update_at: 2026-07-11 15:59
*/
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 55;
int n;
long long m;
string str;
char s[MAXN];
long long cap_arr[MAXN]; // 每个桶的容量
long long cur[MAXN]; // 当前牛奶量
long long nxt[MAXN]; // 下一分钟牛奶量
int pre_pos(int x) {
if (x == 1) return n;
return x - 1;
}
int next_pos(int x) {
if (x == n) return 1;
return x + 1;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
cin >> str;
for (int i = 1; i <= n; i++) {
s[i] = str[i - 1];
}
for (int i = 1; i <= n; i++) {
cin >> cap_arr[i];
cur[i] = cap_arr[i];
}
// 小数据直接逐分钟模拟同时传奶。
for (long long t = 1; t <= m; t++) {
for (int i = 1; i <= n; i++) {
nxt[i] = cur[i];
}
for (int i = 1; i <= n; i++) {
if (cur[i] == 0) continue;
nxt[i]--;
if (s[i] == 'L') {
nxt[pre_pos(i)]++;
} else {
nxt[next_pos(i)]++;
}
}
for (int i = 1; i <= n; i++) {
cur[i] = min(nxt[i], cap_arr[i]);
}
}
long long ans = 0;
for (int i = 1; i <= n; i++) {
ans += cur[i];
}
cout << ans << '\n';
return 0;
}暴力每一分钟复制当前状态,再统一处理所有奶牛的传奶,最后把每个桶限制在容量以内。它的复杂度是
关键是找“什么时候会损失牛奶”。
如果一头奶牛每分钟送出
这种位置只会出现在相邻的 RL 附近:
text
... R R R L L L ...
^ ^中间这对相邻的 R L 是两个可能溢出的点。左边那个 R 点的额外牛奶来自它左侧连续的 R 链;右边那个 L 点的额外牛奶来自它右侧连续的 L 链。
以左边这个 R 点为例:
| 位置关系 | 方向 | 是否计入左侧供奶链 |
|---|---|---|
| 更左侧连续若干点 | R |
是 |
当前 R 点 |
R |
否,它是溢出点 |
| 右邻点 | L |
否,它属于另一侧 |
一条供奶链上的牛奶只沿一个方向流动,链内不会凭空损失。它最多能让汇合点损失这条链的容量总和;但每分钟最多损失
于是每条供奶链对答案的扣减是:
做法:
- 先把答案初始化为
。 - 枚举每个相邻位置,如果出现
R L,标记这两个可能溢出的点。 - 对每个左侧溢出点,向左找连续
R链,扣掉。 - 对每个右侧溢出点,向右找连续
L链,扣掉。
所有这样的链互不重叠,总扫描量是线性的。
代码
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-11 15:57
* update_at: 2026-07-11 15:59
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
int n;
long long m;
string str;
char s[MAXN];
long long a[MAXN];
bool loss_left[MAXN]; // loss_left[i]:i 是一段 R 链右端的亏损点
bool loss_right[MAXN]; // loss_right[i]:i 是一段 L 链左端的亏损点
int pre_pos(int x) {
if (x == 1) return n;
return x - 1;
}
int next_pos(int x) {
if (x == n) return 1;
return x + 1;
}
long long calc_left_chain(int pos) {
long long sum = 0;
int j = pre_pos(pos);
while (s[j] == 'R') {
sum += a[j];
j = pre_pos(j);
}
return sum;
}
long long calc_right_chain(int pos) {
long long sum = 0;
int j = next_pos(pos);
while (s[j] == 'L') {
sum += a[j];
j = next_pos(j);
}
return sum;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
cin >> str;
for (int i = 1; i <= n; i++) {
s[i] = str[i - 1];
}
long long ans = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
ans += a[i];
loss_left[i] = false;
loss_right[i] = false;
}
// 相邻的 R L 会形成两个可能溢出的点。
for (int i = 1; i <= n; i++) {
int j = next_pos(i);
if (s[i] == 'R' && s[j] == 'L') {
loss_left[i] = true;
loss_right[j] = true;
}
}
for (int i = 1; i <= n; i++) {
if (loss_left[i]) {
long long sum = calc_left_chain(i);
ans -= min(sum, m);
}
if (loss_right[i]) {
long long sum = calc_right_chain(i);
ans -= min(sum, m);
}
}
cout << ans << '\n';
return 0;
}复杂度
每头奶牛只会被相邻 RL 的某一侧链统计一次。
时间复杂度为
总结
这题不要盯着“每一分钟怎么变化”,而要盯着“牛奶到底在哪里损失”。
只有相邻 RL 汇合处可能溢出,损失量由两侧单向供奶链决定,再用