USACO历年青铜组真题解析 | 2023年2月
欢迎大家订阅我的专栏:算法题解:C++与Python实现!
本专栏旨在帮助大家从基础到进阶 ,逐步提升编程能力,助力信息学竞赛备战!
专栏特色
1.经典算法练习:根据信息学竞赛大纲,精心挑选经典算法题目,提供清晰的代码实现与详细指导,帮助您夯实算法基础。
2.系统化学习路径:按照算法类别和难度分级,从基础到进阶,循序渐进,帮助您全面提升编程能力与算法思维。
适合人群:
- 准备参加蓝桥杯、GESP、CSP-J、CSP-S等信息学竞赛的学生
- 希望系统学习C++/Python编程的初学者
- 想要提升算法与编程能力的编程爱好者
附上汇总贴:USACO历年青铜组真题解析 | 汇总-CSDN博客
P9121 Hungry Cow
【题目来源】
洛谷:[P9121 USACO23FEB] Hungry Cow B - 洛谷
【题目描述】
Bessie 是一头饥饿的奶牛。每天晚餐时,如果谷仓中有干草,她会吃掉一捆干草。为了防止 Bessie 挨饿,有些天 Farmer John 会在早晨(晚餐之前)送来一些干草。具体地说,在第 \(d_i\) 天,Farmer John 送来了 \(b_i\) 捆干草(\(1 \leq d_i \leq 10^{14}, 1 \leq b_i \leq 10^9\))。
请计算 Bessie 在前 \(T\) 天内共吃掉了多少捆干草。
【输入】
第一行包含两个整数 \(N\) 和 \(T\)(\(1 \leq N \leq 10^5, 1 \leq T \leq 10^{14}\))。
接下来的 \(N\) 行每行包含两个整数 \(d_i\) 和 \(b_i\)。保证 \(1 \leq d_1 < d_2 < \cdots < d_N \leq T\)。
【输出】
输出 Bessie 在前 \(T\) 天内吃掉的干草总数。
注意,本题中涉及的整数可能非常大,可能需要使用 64 位整数类型(例如 C/C++ 中的 "long long")。
【输入样例】
1 5
1 2
【输出样例】
2
【解题思路】

【算法标签】
《洛谷 P9121 Hungry Cow》 #模拟# #贪心# #USACO# #2023#
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n;
long long t, nowd, nowb, lastd, lastb, ans=0;
int main()
{
cin >> n >> t; // 输入n和t
cin >> lastd >> lastb; // 输入第i天,以及干草堆数,将其标记为last,后面循环每次都需要和前一天进行比较
if (n==1) { // 特判n为1,直接输出干草堆数
cout << lastb << endl;
return 0;
}
for (int i=2; i<=n; i++) { // for循环遍历之后n-1次
cin >> nowd >> nowb; // 输入第i天,以及这天的干草堆数
if (nowd<t) { // 如果这一天比询问的t天小
if ((nowd-lastd)>lastb) { // 则判断与上次的天数之差大于干草堆数
ans += lastb; // 说明干草不够相差的这么多天每天吃,只能吃之前的干草堆数
lastb = nowb; // 更新lastb和lastd
lastd = nowd;
} else { // 如果与上次的天数之差小于等于干草堆数
ans += (nowd-lastd); // 则吃了这么多天的干草
lastb = lastb - (nowd-lastd) + nowb; // 剩余的干草要添加到lastb中
lastd = nowd; // 更新lastd
}
}
else if (nowd==t) { // 如果输入的天数为t天
if ((nowd-lastd)>lastb) { // 同样判断与上次的天数之差大于干草堆数
ans += lastb; // 说明干草不够相差的这么多天每天吃,只能吃之前的干草堆数
ans += 1; // 第t天还需要再吃一堆
cout << ans << endl; // 输出结果
return 0; // 并退出程序
} else { // 如果与上次的天数之差小于等于干草堆数
ans += (nowd-lastd); // 则吃了这么多天的干草
ans += 1; // 同样第t天需要再吃一堆
cout << ans << endl; // 输出结果
return 0; // 并退出程序
}
}
}
if (t>lastd) { // 如果t大于n次数的最后一天,则一样要做判断
if ((t-lastd)>lastb) { // 如果与上次的天数之差大于干草堆数
ans += lastb; // 说明只能吃之前的干草堆数
} else { // 如果小于等于干草堆数
ans += (t-lastd)+1; // 则吃了相差天数+1的干草
}
}
cout << ans << endl; // 输出结果
return 0;
}
// 学而思课堂笔记
#include <bits/stdc++.h>
using namespace std;
int n;
long long t, rest, cnt, ans, d[100005], b[100005];
int main()
{
cin >> n >> t;
for (int i=1; i<=n; i++) { // 循环枚举每次送干草信息
cin >> d[i] >> b[i]; // 输入本次送干草时间d[i]和数量b[i]
cnt = min(rest, d[i]-d[i-1]); //计算上一次送甘草和这次送甘草之间奶牛吃掉的干草数量cnt
rest -= cnt; // 吃掉这部分干草
ans += cnt;
rest += b[i]; // 加上这次新送来的干草
}
cout << ans + min(rest, t-d[n]+1) << endl; // 加上最后一次送干草之后吃掉的干草数量并输出
return 0;
}
【运行结果】
1 5
1 2
2
P9122 Stamp Grid
【题目来源】
洛谷:[P9122 USACO23FEB] Stamp Grid B - 洛谷
【题目描述】
盖章绘画是一幅黑白画,绘制在一个 \(N \times N\) 的画布上,其中某些格子被涂黑,而其他格子为空白。它可以用一个 \(N \times N\) 的字符数组表示(\(1 \leq N \leq 20\))。如果数组的第 \(i\) 行第 \(j\) 列的值为 *,说明该格子被涂黑;如果为 .,则说明该格子为空白。
Bessie 想要完成一幅盖章绘画,因此 Farmer John 借给了她一块 \(K \times K\)(\(1 \leq K \leq N\))的盖章,以及一块空的 \(N \times N\) 画布。Bessie 可以将盖章顺时针旋转 \(90^\circ\),并在画布上的任意位置盖章,只要盖章完全在画布范围内即可。形式化地说,盖章时,Bessie 选择整数 \(i,j\),满足 \(i \in [1,N-K+1]\) 且 \(j \in [1,N-K+1]\);对于每个 \((i',j')\),其中 \(1 \leq i',j' \leq K\),画布上的格子 \((i+i'-1,j+j'-1)\) 会被涂黑,如果盖章在 \((i',j')\) 处有墨迹。Bessie 可以在每次盖章之前旋转盖章。一旦画布上的某个格子被涂黑,就会保持涂黑状态。
Farmer John 想知道,Bessie 是否可以用他的盖章完成她想要的盖章绘画。对于每个 \(T\)(\(1 \leq T \leq 100\))个测试用例,帮助 Farmer John 回答这个问题。
【输入】
第一行包含一个整数 \(T\),表示测试用例的数量。
每个测试用例以一个整数 \(N\) 开始,接下来是 \(N\) 行,每行包含由 * 和 . 构成的字符串,表示 Bessie 想要的盖章绘画。接下来的行包含一个整数 \(K\),随后是 \(K\) 行,每行包含由 * 和 . 构成的字符串,表示 Farmer John 的盖章。
相邻的测试用例之间用空行分隔。
【输出】
对于每个测试用例,输出一行 YES 或 NO。
【输入样例】
4
2
**
*.
1
*
3
.**
.**
***
2
.*
**
3
...
.*.
...
3
.*.
...
...
3
**.
.**
..*
2
.*
*.
【输出样例】
YES
YES
NO
YES
【解题思路】

【算法标签】
《洛谷 P9122 Stamp Grid》 #模拟# #USACO# #2023#
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int t, n, k;
char a1[25][25], a2[25][25], b1[25][25], b2[25][25], b3[25][25], b4[25][25];
void paint(char a[][25], char b[][25], char a2[][25]) // 定义对比并画画的自定义函数
{
for (int i=0; i<n-k+1; i++) { // a1矩阵的顶点坐标i和j,从0 - n-k+1
for (int j=0; j<n-k+1; j++) {
int mark = 1; // 定义标记位mark,初始为1
for (int x=i; x<i+k; x++) { // 遍历画布中邮票大小的区域
for (int y=j; y<j+k; y++) {
if (b[x-i][y-j]=='*' && a[x][y]!='*') { // 该区域与邮票对比(使用x-i和y-j,保证邮票的坐标永远是0,0 0,1 1,0 1,1),对于邮票中的'*',如果目的邮票画对应的位置也为'*'
mark = 0; // 修改标记位为0
break; // 退出循环
}
}
if (mark==0) break; // 如果mark为0,退出循环
}
if (mark == 1) { // 如果mark为1
for (int x = i; x<i+k; x++) { // 遍历画布中邮票大小的区域
for (int y=j; y<j+k; y++) {
if (b[x-i][y-j]=='*' && a2[x][y]=='.') { // 对于邮票涂墨且画布中空白的地方
a2[x][y] = b[x-i][y-j]; //在画布的对应位置涂成黑色
}
}
}
}
}
}
}
int main()
{
cin >> t; // 输入t组数据
while (t--) { // 遍历t组数据
cin >> n; // 输入n
for (int i=0; i<n; i++) { // 输入n*n的邮票画
for (int j=0; j<n; j++) {
cin >> a1[i][j];
}
}
cin >> k; // 输入k
for (int i=0; i<k; i++) { // 输入k*k的邮票
for (int j=0; j<k; j++) {
cin >> b1[i][j];
}
}
for (int i=0; i<k; i++) { // 旋转90度
for (int j=0; j<k; j++) {
b2[i][j] = b1[k-1-j][i];
}
}
for (int i=0; i<k; i++) { // 再旋转90度(相当于原有邮票旋转180度)
for (int j=0; j<k; j++) {
b3[i][j] = b2[k-1-j][i];
}
}
for (int i=0; i<k; i++) { // 再旋转90度(相当于原有邮票旋转270度)
for (int j=0; j<k; j++) {
b4[i][j] = b3[k-1-j][i];
}
}
memset(a2, '.', sizeof(a2)); // 每次初始化n*n的画布
paint(a1, b1, a2); // 用b1去比对a1,并画画
paint(a1, b2, a2); // 用b2去比对a1,并画画
paint(a1, b3, a2); // 用b3去比对a1,并画画
paint(a1, b4, a2); // 用b4去比对a1,并画画
int mark = 0; // 定义mark标记位
for (int i=0; i<n; i++) { // 检查a1与a2是否完全匹配
for (int j=0; j<n; j++) {
if (a1[i][j]==a2[i][j]) mark = 1; // 匹配时mark标记为1
else {
mark = 0; // 如果遇到有个不匹配的,mark修改为0
break; // 并退出循环
}
}
if (mark==0) break; // 如果mark为0,再退出外部循环
}
if (mark==1) cout << "YES" << endl; // 最后mark为1就输出YES
else cout << "NO" << endl; // 否则输出NO
}
return 0;
}
// 学而思课堂笔记
#include <bits/stdc++.h>
using namespace std;
int T, n, k;
char a[25][25], c[25][25], t[25][25], d[25][25]; // a表示目标画作,c表示印章,t表示印章旋转90度的过渡数组,d表示空白画布
void rotate() // 将印章顺时针旋转90°,
{
// 第一步:将印章数组c顺时针旋转90°存入过渡数组t
for (int i=1; i<=k; i++) {
for (int j=1; j<=k; j++) {
t[j][k+1-i] = c[i][j];
}
}
// 第二步:将过渡数组t复制回原印章数组c
for (int i=1; i<=k; i++) {
for (int j=1; j<=k; j++) {
c[i][j] = t[i][j];
}
}
}
void paint(int x, int y) // 在空白画布(数组d)上盖章染色(印章的左上角与画布的(x,y)重叠)
{
// 第一步:检查此处是否可以盖章染色
for (int i=x; i<=x+k-1; i++) {
for (int j=y; j<=y+k-1; j++) {
if (c[i-x+1][j-y+1]=='*' && a[i][j]!='*') {
return;
}
}
}
// 第二步:第一步没有return表示此处可以盖章染色,就染色即可
for (int i=x; i<=x+k-1; i++) {
for (int j=y; j<=y+k-1; j++) {
if (c[i-x+1][j-y+1]=='*') {
d[i][j] = '*';
}
}
}
}
void check()
{
for (int i=1; i<=n; i++) {
for (int j=1; j<=n; j++) {
if (a[i][j]=='*' && d[i][j]!='*') {
cout << "NO" << endl;
return;
}
}
}
cout << "YES" << endl;
}
int main()
{
cin >> T;
while (T--) {
cin >> n;
for (int i=1; i<=n; i++) {
for (int j=1; j<=n; j++) {
cin >> a[i][j];
d[i][j] = '.';
}
}
cin >> k;
for (int i=1; i<=k; i++) {
for (int j=1; j<=k; j++) {
cin >> c[i][j];
}
}
for (int p=1; p<=4; p++) {
rotate();
for (int x=1; x<=n-k+1; x++) {
for (int y=1; y<=n-k+1; y++) {
paint(x, y);
}
}
}
check();
}
return 0;
}
【运行结果】
4
2
**
*.
1
*
YES
3
.**
.**
***
2
.*
**
YES
3
...
.*.
...
3
.*.
...
...
NO
3
**.
.**
..*
2
.*
*.
YES
P9123 Watching Mooloo
【题目来源】
洛谷:[P9123 USACO23FEB] Watching Mooloo B - 洛谷
【题目描述】
贝茜喜欢看 Mooloo 的演出。因为她是一只忙碌的奶牛,她计划在接下来的 \(N (1 \le N \le 10^5)\) 天去看演出。因为 Mooloo 提供了订阅服务,她想要使她花费的钱最少。
Mooloo 有一个有趣的订阅服务系统:若要在此之后的连续 \(d\) 天看演出,则在订阅时需要花费 \(d+K(1 \le K \le 10^9)\) 个单位价格。你可以随时订阅;若本次订阅已经过期,你可以根据需要订阅多次。基于以上条件,请计算出贝茜最少要花费多少个单位价格,才能完成她的计划。
【输入】
第一行输入两个正整数 \(N\) 和 \(K\)。
第二行输入 \(N\) 个正整数,表示在这些天里,贝茜会看 Mooloo 的演出:\(1 \le d_1<d_2<\cdots<d_N \le 10^{14}\)。
【输出】
请注意,此问题中可能需要使用 64 位整数数据类型(如 C 或 C++ 中的 long long)。
【输入样例】
2 4
7 9
【输出样例】
7
【解题思路】

【算法标签】
《洛谷 P9123 Watching Mooloo》 #USACO# #2023# #贪心#
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n, k;
long long a[100005], dp[100005], sum=0;
int main()
{
cin >> n >> k; // 输入n和k
for (int i=1; i<=n; i++) { // 输入哪些天要看演出
cin >> a[i];
}
dp[1] = 1+k; // 第一天为1+k
for (int i=2; i<=n; i++) { // 遍历后面n-1天
dp[i] = min(dp[i-1]+1+k, dp[i-1]+a[i]-a[i-1]); // dp递推式,取1+k和a[i]-a[i-1]的最小值
}
cout << dp[n] << endl; // 输出最后一天的价格,就是总计最小值
return 0;
}
// 学而思课堂笔记
#include <bits/stdc++.h>
using namespace std;
int n, k;
long long ans, d[100005];
int main()
{
cin >> n >> k;
for (int i=1; i<=n; i++) {
cin >> d[i];
if (i==1) ans = k+1; // 如果是开始订阅的第一天
else { // 如果不是开始订阅的第一天
long long x = d[i] - d[i-1]; // 续约的订阅花费
long long y = k+1; // 重新订阅花费
ans += min(x, y);
}
}
cout << ans << endl;
return 0;
}
【运行结果】
2 4
7 9
7

浙公网安备 33010602011771号