题解:洛谷 P8898 Feeding the Cows

【题目来源】

洛谷:P8898 [USACO22DEC] Feeding the Cows B - 洛谷

【题目描述】

Farmer John 有 \(N(1\le N\le 10^5)\) 头奶牛,每头奶牛的品种是更赛牛(Guernsey)或荷斯坦牛(Holstein)之一。她们沿水平方向排成一行,奶牛们占据的位置编号为 \(1\dots N\)

由于奶牛们都饿了,FJ 决定在 \(1\)\(\dots N\) 中的某些位置上种植草地。更赛牛和荷斯坦牛喜欢不同类型的草,所以如果 Farmer John 决定在某个位置种草,他必须选择种植更赛牛喜欢的草或荷斯坦牛喜欢的草——他不能在同一个位置同时种两种草。种植的每一片草地都可以喂饱数量不限的相应品种的奶牛。

每头奶牛愿意移动至多 \(K(0\le K\le N-1)\)个位置以前往一个草地。求出喂饱所有奶牛所需种植的最小草地数量。此外,输出一种使用最小草地数量喂饱所有奶牛的种植方案。任何满足上述条件的方案均视为正确。

【输入】

每个测试用例包含 \(T\) 个子测试用例,为一种奶牛的排列。输入的第一行包含 \(T(1\le T\le 10)\)。以下是 \(T\) 个子测试用例。

每个子测试用例的第一行包含 \(N\)\(K\)。第二行包含一个长度为 \(N\) 的字符串,其中第 \(i\) 个字符表示第 \(i\) 头奶牛的品种(G 表示更赛牛,H 表示荷斯坦牛)。

【输出】

对于 \(T\) 个子测试用例中的每一个,输出两行。第一行输出喂饱所有奶牛所需的最小草地数量。第二行输出一个长度为 \(N\) 的字符串,表示一种使用最小草地数量喂饱所有奶牛的种植方案。第 \(i\) 个字符表示第 \(i\) 个位置,若不种草则为 .,若种植喂饱更赛牛的草则为 G,若种植喂饱荷斯坦牛的草则为 H。任何合法的方案均可通过。

【输入样例】

6
5 0
GHHGG
5 1
GHHGG
5 2
GHHGG
5 3
GHHGG
5 4
GHHGG
2 1
GH

【输出样例】

5
GHHGG
3
.GH.G
2
..GH.
2
...GH
2
...HG
2
HG

【核心思想】

  1. 问题分析:给定 \(N\) 头奶牛排成一行,每头是 G(更赛牛)或 H(荷斯坦牛)。需在若干位置种草(G 草或 H 草),每头奶牛可移动至多 \(K\) 个位置去吃对应品种的草。求最小草地数量及一种种植方案。本质是一个贪心区间覆盖问题。

  2. 算法选择

    • 贪心扫描 + 双品种独立处理:分别处理 G 牛和 H 牛,每次为当前未覆盖的牛在其最远可达位置种草,然后跳过所有能被该草地覆盖的牛
  3. 关键步骤

    • 初始化ans 数组全部为 .(未种草),计数器 \(\text{cnt} = 0\)
    • 处理 G(从左到右扫描):
      • 找到当前第一个未被覆盖的 G 牛位置 \(i\)
      • 在位置 \(\min(i + K, N - 1)\)(或 \(N\))种植 G
      • 该草地可覆盖 \([i, i + 2K]\) 范围内的 G 牛,将扫描位置跳到 \(i + 2K + 1\)
      • 若越界则在末尾种植
    • 处理 H(类似逻辑):
      • 找到当前第一个未被覆盖的 H 牛位置 \(i\)
      • 在位置 \(\min(i + K, N - 1)\) 种植 H
      • 冲突处理:若目标位置已有 G 草,则向左寻找最近的空位种植 H
      • 跳到 \(i + 2K + 1\)
    • 输出:统计 ans 中非 . 的位置数,输出数量和方案
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\),每头奶牛最多被访问一次
    • 空间复杂度:\(O(N)\),存储奶牛排列和种植方案
  5. 贪心最远覆盖的核心思想

    • 最优覆盖区间:为位置 \(i\) 的牛种草时,种在 \(i + K\) 处最优——这样既能覆盖该牛,又能尽可能向右延伸覆盖更多牛(覆盖范围为 \([i, i + 2K]\)
    • 双品种独立处理GH 的草地互不干扰(除位置冲突外),分别贪心处理各自品种
    • 跳跃式扫描:种完一块草地后,直接跳到覆盖范围之外,避免重复检查已覆盖的牛
    • 边界冲突处理:当 H 草的目标位置被 G 草占用时,向左寻找空位,确保 H 牛仍能被覆盖(因为向左移动不超过 \(K\) 格仍在覆盖范围内)
    • 适用于区间覆盖、资源分配、贪心选址类问题

【解题思路】

【算法标签】

普及 #模拟

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int t, n, k;
char cow[100005]={0}, ans[100005];  // 定义cow数组存放奶牛品种,ans数组存放种植的草
int main()
{
    cin >> t;  // 输入t
    for (int i=1; i<=t; i++) {  // for循环遍历t个测试用例
        int cnt = 0;  // 初始化最小草地数量,初始为0
        memset(ans, '.', sizeof(ans));  //每次循环初始化草地数组
        cin >> n >> k;  // 输入n和k
        for (int j=1; j<=n; j++) {  // 遍历n个字符
            cin >> cow[j];  // 记录在cow数组中
        }
        // 先遍历一遍,种植G草
        int mark=1;  // 定义起始坐标
        while (mark<=n) {  // 只要坐标没有越界
            if (cow[mark]=='G') {  // 如果该坐标位置为G牛
                if ((mark+k)<n) {  // 如果mark+k没有越边界
                    ans[mark+k] = 'G';  // 则在mark+k的位置种植G草
                    mark = mark+2*k+1;  // 并把位置修改为mark+2k+1,因为mark到mark+2k之间所有的牛都可以吃mark+k位置的草
                } else {  // 如果坐标越界
                    ans[n] = 'G';  // 则最后一个位置种植G草
                    break;  // 退出循环
                }
            } else mark++;  // 否则mark自增,直到找到位置为G牛的坐标
        }
        // 再遍历一遍,种植H草
        int mark2=1;  // 定义起始坐标
        while (mark2<=n) {  // 只要坐标没有越界
            if (cow[mark2]=='H') {  // 如果该坐标位置为H牛
                if ((mark2+k)<n) {  // 如果mark+k小于n
                    ans[mark2+k] = 'H';  // 则在mark+k的位置种植H草
                    mark2 = mark2+2*k+1;  // 并把位置修改为mark+2k+1,因为mark到mark+2k之间所有的牛都可以吃mark+k位置的草
                } else {  // 如果mark+k>=n
                    if (ans[n]=='G') {  // 判断n这个位置是否有G草
                        ans[n-1] = 'H';  // 如果有,则在n-1的位置种上H草
                        break;  // 退出循环
                    } else {  // 如果没有
                        ans[n] = 'H';  // 则种上H草
                        break;  // 退出循环
                    }
                }
            } else mark2++;  // 否则mark自增,直到找到位置为H牛的坐标
        }
        for (int i=1; i<=n; i++) {  // for循环遍历n个位置
            if (ans[i]!='.') cnt++;  // 对于种草的位置,cnt自增
        }
        cout << cnt << endl;  // 输出草地数量
        for (int i=1; i<=n; i++) {  // 再for循环遍历n个位置
            cout << ans[i];  // 输出种植的草(有些题有多个解,但不影响结果判断)
        }
        cout << endl;
    }
    return 0;
}
//学而思课堂笔记
#include <bits/stdc++.h>
using namespace std;
int T;
int main()
{
    cin >> T;
    while (T--) {
        int n, k, cnt=0;
        string s, t = "";
        cin >> n >> k >> s;
        t = string(n, '.');

        // 先考虑更赛牛
        for (int i=0; i<=n-1; i++) {
            if (s[i]=='G') {
                int pos = min(i+k, n-1);  // 对于位置i的更赛牛,在位置i+k种植一块草地
                t[pos] = 'G';
                cnt++;
                i = min(pos+k, n-1);
            }
        }
        //再考虑荷斯坦牛
        for (int i=0; i<=n-1; i++) {
            if (s[i]=='H') {
                int pos = min(i+k, n-1);  // 对于位置i的荷斯坦牛,在位置i+k种植一块草地
                while (t[pos]!='.') pos--;
                t[pos] = 'H';
                cnt++;
                i = min(pos+k, n-1);
            }
        }
        cout << cnt << endl;
        cout << t <<endl;
    }
    return 0;
}

【运行结果】

6
5 0
GHHGG
5
GHHGG
5 1
GHHGG
3
.GH.G
5 2
GHHGG
2
..GH.
5 3
GHHGG
2
...GH
5 4
GHHGG
2
...HG
2 1
GH
2
HG
posted @ 2026-08-29 09:40  团爸讲算法  阅读(3)  评论(0)    收藏  举报