1

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 的盖章。

相邻的测试用例之间用空行分隔。

【输出】

对于每个测试用例,输出一行 YESNO

【输入样例】

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
posted @ 2026-02-03 11:08  热爱编程的通信人  阅读(0)  评论(0)    收藏  举报