题解:洛谷 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

【核心思想】

  1. 问题分析:贝西从位置 \(0\) 出发,沿直线依次经过位置 \(1\)\(N\) 的奶牛。\(D_i > 0\) 表示奶牛 \(i\) 欠贝西钱,\(D_i < 0\) 表示贝西欠奶牛 \(i\) 钱。贝西可以在有足够钱时随时还债,求收完所有债并还清所有欠款的最短行走距离。本质上是贪心模拟 + 往返路径计算问题:维护当前资金,当资金从负变正时计算往返还债的额外路程。

  2. 算法选择

    • 顺序扫描:从左到右依次处理每头奶牛
    • 资金追踪\(sum\) 记录当前累计资金,\(flag\) 标记是否处于欠债状态,\(l\) 记录欠债开始位置
  3. 关键步骤

    • 读取数据:读入 \(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\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\),线性扫描
    • 空间复杂度:\(O(1)\),仅使用常数额外变量
  5. 贪心模拟的核心思想

    • 债务区间的合并:当资金从正变负时,标记债务区间起点;当资金从负变正时,债务区间结束,需要往返该区间还债
    • 往返距离计算:从位置 \(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
posted @ 2026-08-17 09:45  团爸讲算法  阅读(17)  评论(0)    收藏  举报