题解:洛谷 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
【核心思想】
-
问题分析:给定 \(N\) 头奶牛排成一行,每头是
G(更赛牛)或H(荷斯坦牛)。需在若干位置种草(G草或H草),每头奶牛可移动至多 \(K\) 个位置去吃对应品种的草。求最小草地数量及一种种植方案。本质是一个贪心区间覆盖问题。 -
算法选择:
- 贪心扫描 + 双品种独立处理:分别处理
G牛和H牛,每次为当前未覆盖的牛在其最远可达位置种草,然后跳过所有能被该草地覆盖的牛
- 贪心扫描 + 双品种独立处理:分别处理
-
关键步骤:
- 初始化:
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中非.的位置数,输出数量和方案
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(N)\),每头奶牛最多被访问一次
- 空间复杂度:\(O(N)\),存储奶牛排列和种植方案
-
贪心最远覆盖的核心思想:
- 最优覆盖区间:为位置 \(i\) 的牛种草时,种在 \(i + K\) 处最优——这样既能覆盖该牛,又能尽可能向右延伸覆盖更多牛(覆盖范围为 \([i, i + 2K]\))
- 双品种独立处理:
G和H的草地互不干扰(除位置冲突外),分别贪心处理各自品种 - 跳跃式扫描:种完一块草地后,直接跳到覆盖范围之外,避免重复检查已覆盖的牛
- 边界冲突处理:当
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
浙公网安备 33010602011771号