题解:洛谷 P1599 结算日
【题目来源】
洛谷:P1599 [USACO09MAR] 结算日 Payback B - 洛谷(luogu.com.cn)
【题目描述】
“不放债不借债”,贝西多么希望自己可以遵循这个忠告。她已经和她的 \(N(1 \leq N \leq 100,000)\) 个朋友有了债务关系,或者借债了,或者放债了。她的 \(N\) 个朋友依次标号为 \(1\dots N\)。
结算日终于来临了。她知道,朋友欠她的钱比她欠朋友的钱多。她的朋友们分布在一条直线上,第 \(i\) 头奶牛站的位置距离谷仓 \(i\) 米。贝西打算沿着这条直线行走,从欠她钱的奶牛手里收钱回来,并且还钱给她欠钱的奶牛。
当她沿直线移动的时候,她可以要求任何欠她钱的奶牛还全部的钱。当她有足够的钱可以还清她的某个债,就可以把钱给对应的奶牛还清她的债。奶牛 \(i\) 欠贝西 \(D_i\) 元 \((-1,000 \leq D_i \leq 1,000,D_i \neq 0)\),负数表示贝西欠奶牛 \(i\) 钱。
贝西从谷仓出发,位置为 \(0\),初始贝西没有钱。贝西收回她的所有借债,并且还清她的欠债所需行走的最短距离是多少?
注意:她必须在最后一头奶牛所在的位置,完成她的行走。
【输入】
第一行,一个整数 \(N\)。
接下来第 \(2 \dots N+1\) 行,第 \(i+1\) 行包含一个整数 \(D_i\)。
【输出】
一个整数,贝西收回借债并且还清欠债,所需要行走的最短距离(单位为米)。
【输入样例】
5
100
-200
250
-200
200
【输出样例】
9
【核心思想】
-
问题分析:贝西从位置 \(0\) 出发,沿直线依次经过位置 \(1\) 到 \(N\) 的奶牛。\(D_i > 0\) 表示奶牛 \(i\) 欠贝西钱,\(D_i < 0\) 表示贝西欠奶牛 \(i\) 钱。贝西可以在有足够钱时随时还债,求收完所有债并还清所有欠款的最短行走距离。本质上是贪心模拟 + 往返路径计算问题:维护当前资金,当资金从负变正时计算往返还债的额外路程。
-
算法选择:
- 顺序扫描:从左到右依次处理每头奶牛
- 资金追踪:\(sum\) 记录当前累计资金,\(flag\) 标记是否处于欠债状态,\(l\) 记录欠债开始位置
-
关键步骤:
- 读取数据:读入 \(N\) 和 \(D_1, \dots, D_N\)
- 顺序扫描(\(i = 1\) 到 \(N\)):
- \(sum \leftarrow sum + D_i\)(累加资金)
- \(ans \leftarrow ans + 1\)(从上一位置走到当前位置,距离 \(1\))
- 若 \(sum \geq 0\) 且 \(flag = 1\)(欠债已还清):
- \(flag = 0\)
- \(ans \leftarrow ans + (i - l) \times 2\)(往返欠债区间的额外距离)
- 若 \(sum < 0\) 且 \(flag = 0\)(开始欠债):
- \(flag = 1\)
- \(l = i\)(记录欠债开始位置)
- 输出 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N)\),线性扫描
- 空间复杂度:\(O(1)\),仅使用常数额外变量
-
贪心模拟的核心思想:
- 债务区间的合并:当资金从正变负时,标记债务区间起点;当资金从负变正时,债务区间结束,需要往返该区间还债
- 往返距离计算:从位置 \(i\) 返回到欠债起点 \(l\) 再返回 \(i\),额外距离为 \(2 \times (i - l)\)
- 顺序处理的正确性:贝西必须按顺序经过所有奶牛,无法跳过,因此债务只能在当前位置或之后还清
- 最终位置约束:题目要求必须在最后一头奶牛位置完成行走,因此不需要从 \(N\) 返回 \(0\)
- 适用于路径规划、债务清算、贪心模拟类问题
【算法标签】
普及- #模拟
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n; // 奶牛的数量
int tmp; // 临时变量,存储当前奶牛的钱
int sum; // 当前累计的钱数(正数表示有盈余,负数表示欠债)
int ans; // 总路程(答案)
int l; // 记录开始欠债的位置
int flag = 0; // 标记是否处于欠债状态(0:无欠债,1:有欠债)
int main()
{
// 输入奶牛数量
cin >> n;
// 依次处理每头奶牛
for (int i = 1; i <= n; i++)
{
// 输入当前奶牛的钱
cin >> tmp;
// 累加钱数
sum += tmp;
// 每次处理一头奶牛,路程增加1(从当前位置到下一头奶牛)
ans++;
// 情况1:如果当前累计钱数>=0且之前有欠债
if (sum >= 0 && flag == 1)
{
// 欠债已还清,清除欠债标记
flag = 0;
// 计算往返路程:从当前位置返回到欠债位置再回来
// 往返距离 = 2 × (当前位置 - 欠债开始位置)
ans += (i - l) * 2;
}
// 情况2:如果当前累计钱数<0且之前没有欠债
if (sum < 0 && flag == 0)
{
// 开始欠债,记录欠债开始位置
flag = 1;
l = i; // 记录欠债开始的位置
}
}
// 输出总路程
cout << ans << endl;
return 0;
}
【运行结果】
5
100
-200
250
-200
200
9
浙公网安备 33010602011771号