[NOIP2004-普及] 花生采摘

GitHub跳转原题关系图返回列表

按花生数从大到小模拟采摘顺序,每次检查采摘后能否按时跳回路边,贪心即可;复杂度 O(MN log MN)。

OJ: roj

题目 ID: 3529

难度:普及-

标签:贪心模拟排序

创建: 2026-10-02 05:39

更新: 2026-10-07 13:50

题目描述

M×NM \times N 花生田(M,N≤20M,N \le 20,花生数互不相同),多多每次跳到第一行某植株、四邻移动、采摘或跳回路边,各占 1 时间,总时间 KK。采摘顺序固定为花生数从大到小,求限时内最多采多少花生。

思路

按花生数降序模拟:进田到首棵花 rr 步、植株间转移花曼哈顿距离、采摘花 1 步、返回花 rr 步;每采一株后检查剩余时间是否够返回,不够即停(后面花生更少,采了来不及返回不优)。

参考代码

Python 版:

python
import sys

def main() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    m, n, k = next(data), next(data), next(data)
    # 收集所有长花生的植株 (花生数, 行, 列),按花生数从大到小排序
    nuts = sorted(
        [(p, i, j) for i in range(m) for j in range(n) if (p := next(data)) > 0],
        key=lambda x: -x[0],
    )
    t = ans = 0  # t: 已用时间;ans: 已采到的花生总数
    r = c = -1  # 当前位置(-1 表示还在路边)
    for p, nr, nc in nuts:
        # 从路边/上一棵植株移动到 (nr,nc) 再采摘:第一段路要 nr+1 步,其余走曼哈顿距离
        t += (nr + 1 if r < 0 else abs(nr - r) + abs(nc - c)) + 1
        if t + nr + 1 > k:  # 采完后还要 nr+1 步跳回路边,回不去就不采(后面的花生更少,直接停)
            break
        ans += p
        r, c = nr, nc
    print(ans)

main()

C++ 版(同一算法):

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-10-06 13:11
 * update_at: 2026-10-06 13:11
 */
#include <cstdio>
#include <cstdlib>
#include <algorithm>
typedef long long ll;

const int MAXN = 25;   // 网格最大 20x20

int m, n, k;
int a[MAXN][MAXN];     // a[i][j] 表示植株 (i,j) 下的花生数,0 表示没有花生
ll ans = 0;            // 采到的花生总数

// 按"从大到小"依次找下一个最大花生的植株:返回是否找到
bool find_max(int &mr, int &mc, int &mv) {
    mv = 0;
    for (int i = 1; i <= m; ++i)
        for (int j = 1; j <= n; ++j)
            if (a[i][j] > mv) {
                mv = a[i][j];
                mr = i;
                mc = j;
            }
    return mv > 0; // 没有剩余花生时返回 false
}

int main() {
    scanf("%d %d %d", &m, &n, &k);
    for (int i = 1; i <= m; ++i)
        for (int j = 1; j <= n; ++j)
            scanf("%d", &a[i][j]);

    ll t = 0;        // 已用时间
    int r = 0, c = 0; // 当前位置,0 表示还在路边

    int mr, mc, mv;
    while (find_max(mr, mc, mv)) {
        // 从当前位置移动到 (mr,mc) 再采摘
        if (r == 0)
            t += mr + 1;                          // 从路边进田:mr 次向下 + 1 次采摘
        else
            t += std::abs(mr - r) + std::abs(mc - c) + 1; // 植株间移动 + 采摘
        // 采完后还要 mr 步才能跳回路边,回不去就停止(后面的花生更少,不采更优)
        if (t + mr > k) break;

        ans += mv;
        a[mr][mc] = 0; // 摘掉这棵植株的花生
        r = mr;
        c = mc;
    }

    printf("%lld\n", ans);
    return 0;
}