题解:洛谷 P9014 Following Directions

【题目来源】

洛谷:P9014 [USACO23JAN] Following Directions S - 洛谷

【题目描述】

Farmer John 有一个正方形的草地,草地被划分为了 \((N+1)\times (N+1)(1\le N\le 1500)\) 的格子。设 \((i,j)\) 为从上到下、从左到右第 \(i\) 行,第 \(j\) 列的格子。每个满足 \(1\le i,j\le n\) 的格子 \((i,j)\) 之中都住着一头牛,而且每个这样的格子上都有一个路标指向右或下。除此之外,所有满足 \(i=N+1\)\(j=N+1\) 的格子,除了 \((N+1,N+1)\) 都会有一个饲料桶。牛在每个饲料桶进食需要的价格不同;位置 \((i,j)\) 上的桶喂饱一只牛需要价格 \(c_{i,j}(1\le c_{i,j}\le 500)\)

每天晚饭时间,Farmer John 摇响晚餐铃时,所有牛都沿着路标的指向前进,直到它们遇到了饲料桶,之后它们会在它们自己遇到的饲料桶那里进食。第二天,所有牛又会回到自己原来的位置。

为了维持预算,Farmer John 想要知道每天喂食需要的价钱。然而,每天晚饭之前,总会有一头牛 \((i,j)\) 翻转它那里的路标(原来向下则变成向右,反之亦然)。被翻转的路标指向将在后面的日子里保持不变,除非它又被进行了翻转。

给出每天被翻转的路标的坐标,请输出每天喂食需要的价格(总共有 Q 天,\(1\le Q\le 1500\))。

【输入】

第一行为 \(N(1\le N\le 1500)\)

接下来的 \(N\)\(+1\) 行从上到下输入初始的路标朝向和每个饲料桶的价格 \(c_{i,j}\)。前 \(N\) 行每行包含一个长度为 \(N\) 的字符串,其中每个字符只能是 RDR 表示向右,D 表示向下),之后是一个数,表示价格 \(c_{i,N+1}\),第 \((N+1)\) 行包含 \(N\) 个数,依次表示价格 \(c_{N+1,j}\)

接下来的一行为 \(Q(1\le Q\le 1500)\)

之后的 \(Q\) 行,每行有两个整数 \(i\)\(j(1\le i,j\le N)\),表示每天被翻转的路标的坐标。

【输出】

\(Q+1\) 行:第一行是初始的总价格,之后 \(Q\) 行依次是每次被翻转后的总价格。

【输入样例】

2
RR 1
DD 10
100 500
4
1 1
1 1
1 1
2 1

【输出样例】

602
701
602
701
1501

【核心思想】

  1. 问题分析:给定一个 \((N+1) \times (N+1)\) 的网格,前 \(N \times N\) 每个格子有路标 R(右)或 D(下),边界有饲料桶及价格。每头牛沿路标走到边界进食。每天翻转一个路标,求每天的总喂食价格。本质上是递推计数 + 增量更新问题:用 sum[r][c] 表示经过格子 \((r,c)\) 的牛的数量,递推计算后通过增量更新处理翻转。

  2. 算法选择

    • 递推求路径数sum[r][c] 初始为 \(1\)(每格一头牛),按路标方向累加传播到边界
    • 增量更新:翻转路标时,先沿原方向减去该格子的贡献,翻转后再沿新方向加回贡献
  3. 关键步骤

    • 读取数据:读入 \(N\)\(N \times N\) 路标和行末价格 costR,以及底部价格 costD
    • 初始化sum[r][c] = 1\(1 \leq r, c \leq N\)
    • 递推传播\(r = 1\)\(N\)\(c = 1\)\(N\)):
      • dir[r][c] == 'R'sum[r][c+1] += sum[r][c]
      • dir[r][c] == 'D'sum[r+1][c] += sum[r][c]
    • 计算总价格ans = sum[r][N+1] * costR[r] + sum[N+1][c] * costD[c]
    • 处理 \(Q\) 次翻转
      • 保存 tmpR = r, tmpC = c, tmpSum = sum[r][c]
      • 减去原贡献:沿原路标方向走,每步 sum 减去 tmpSum
      • 翻转路标R \(\leftrightarrow\) D
      • 加回新贡献:沿新路标方向走,每步 sum 加上 tmpSum
      • 输出新的总价格
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N^2 + Q \cdot N)\),初始化 \(O(N^2)\),每次翻转更新 \(O(N)\)
    • 空间复杂度:\(O(N^2)\)sumdir 数组
  5. 递推计数 + 增量更新的核心思想

    • 路径数的叠加原理:每格子的牛按路标流向下一格,sum 数组记录流经每个格子的牛数,边界格子的 sum 即为到达该饲料桶的牛数
    • 增量更新的正确性:翻转一个路标只影响从该格子出发的所有下游路径,减去原路径贡献再加回新路径贡献,其他格子不受影响
    • 单向传播:路标只有 RD 两个方向,保证无环,递推顺序从左到右、从上到下即可
    • 价格计算:边界格子的 sum 乘以对应饲料桶价格即为该桶总花费
    • 适用于网格路径、动态更新、流量统计类问题

【解题思路】

【算法标签】

普及 #递推

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int maxN = 1505;
char dir[maxN][maxN];
int n, sum[maxN][maxN], costR[maxN], costD[maxN];

int calc()
{
    int ans = 0;
    for (int r=1; r<=n; r++) {  // 遍历所有行
        ans += sum[r][n+1] * costR[r];  // 计算每行的价格,并进行累加
    }
    for (int c=1; c<=n; c++) {  // 遍历所有列
        ans += sum[n+1][c] * costD[c];  // 计算每列的价格,并累加
    }
    return ans;  // 返回总价格
}
int main()
{
    cin >> n;  // 输入n
    for (int r=1; r<=n; r++) {  // 输入所有字符
        for (int c=1; c<=n; c++) {
            cin >> dir[r][c];
        }
        cin >> costR[r];  // 每行最后一个数字
    }
    for (int c=1; c<=n; c++) {  /// 输入每列的价格
        cin >> costD[c];
    }
    for (int r=1; r<=n; r++) {  // 每个位置的sum[r][c]初始为1,用于后面递推
        for (int c=1; c<=n; c++) {
            sum[r][c] = 1;
        }
    }
    for (int r=1; r<=n; r++) {  // 进行递推
        for (int c=1; c<=n; c++) {
            if (dir[r][c]=='R') {  // 如果当前字符为'R'
                sum[r][c+1] += sum[r][c];  // 右边的sum[r][c]加上当前值
            }
            if (dir[r][c]=='D') {  // 如果当前字符为'D'
                sum[r+1][c] += sum[r][c];  // 下面的sum[r][c]加上当前值
            }
        }
    }
    cout << calc() << endl;  // 输出未翻转前的总价格
    int q;
    cin >> q;  // 输入q
    while (q--) {  // 遍历q次询问
        int r, c;
        cin >> r >> c;  // 输入被翻转的坐标
        int tmpR = r, tmpC = c, tmpSum = sum[r][c];  // 临时变量用来保存r、c和tmpSum,因为r和c要变化
        while (r<=n && c<=n) {  // 先从r->n和c->n,每个位置减掉tmpSum
            if (dir[r][c] == 'R') {  // 如果当前字符为'R'
                sum[r][++c] -= tmpSum;  // 则右边的sum[r][c]减去tmpSum
            } else {  // 如果当前字符为'D'
                sum[++r][c] -= tmpSum;  // 则下边的sum[r][c]减去tmpSum
            }
        }
        r = tmpR, c = tmpC;  // 还原r和c
        if (dir[r][c]=='R') {  // 设定翻转后的路标
            dir[r][c] = 'D';
        } else {
            dir[r][c] = 'R';
        }
        while (r<=n && c<=n) {  // 再次遍历r->c和c->n,每个位置再加回来tmpSum
            if (dir[r][c] == 'R') {  // 如果当前字符为'R'
                sum[r][++c] += tmpSum;  // 则右边的sum[r][c]减去tmpSum
            } else {  // 如果当前字符为'D'
                sum[++r][c] += tmpSum;  // 则下边的sum[r][c]减去tmpSum
            }
        }
        cout << calc() << endl;  // 输出每轮调整后的总和
    }
    return 0;
}

【运行结果】

2
RR 1
DD 10
100 500
602
4
1 1
701
1 1
602
1 1
701
2 1
1501
posted @ 2026-08-17 22:48  团爸讲算法  阅读(12)  评论(0)    收藏  举报