1

GESP认证C++编程真题解析 | 202312 七级

​欢迎大家订阅我的专栏:算法题解:C++与Python实现
本专栏旨在帮助大家从基础到进阶 ,逐步提升编程能力,助力信息学竞赛备战!

专栏特色
1.经典算法练习:根据信息学竞赛大纲,精心挑选经典算法题目,提供清晰的代码实现与详细指导,帮助您夯实算法基础。
2.系统化学习路径:按照算法类别和难度分级,从基础到进阶,循序渐进,帮助您全面提升编程能力与算法思维。

适合人群:

  • 准备参加蓝桥杯、GESP、CSP-J、CSP-S等信息学竞赛的学生
  • 希望系统学习C++/Python编程的初学者
  • 想要提升算法与编程能力的编程爱好者

附上汇总帖:GESP认证C++编程真题解析 | 汇总


编程题

P10110 商品交易

【题目来源】

洛谷:[P10110 GESP202312 七级] 商品交易 - 洛谷

【题目描述】

市场上共有 \(N\) 种商品,编号从 \(0\)\(N-1\) ,其中,第 \(i\) 种商品价值 \(v_i\) 元。

现在共有 \(M\) 个商人,编号从 \(0\)\(M-1\) 。在第 \(j\) 个商人这,你可以使用你手上的第 \(x_j\) 种商品交换商人手上的第 \(y_j\) 种商品。每个商人都会按照商品价值进行交易,具体来说,如果 \(v_{x_j}>v_{y_j}\),他将会付给你 \(v_{x_j}-v_{y_j}\)元钱;否则,那么你需要付给商人 \(v_{y_j}-v_{x_j}\) 元钱。除此之外,每次交易商人还会收取 \(1\) 元作为手续费,不论交易商品的价值孰高孰低。

你现在拥有商品 \(a\) ,并希望通过一些交换来获得商品 \(b\) 。请问你至少要花费多少钱?(当然,这个最小花费也可能是负数,这表示你可以在完成目标的同时赚取一些钱。)

【输入】

第一行四个整数 \(N , M , a , b\),分别表示商品的数量、商人的数量、你持有的商品以及你希望获得的商品。保证 \(0 \le a,b < N\) ,保证 \(a \ne b\)

第二行 \(N\) 个用单个空格隔开的正整数 \(v_0,v_1,…,v_{N-1}\) ,依次表示每种商品的价值。保证 \(1≤v_i≤10^9\)

接下来 \(M\) 行,每行两个整数 \(x_j,y_j\) ,表示在第 \(j\) 个商人这,你可以使用第 \(x_j\) 种商品交换第 \(y_j\) 种商品。保证 \(0≤x_j,y_j<N\),保证 \(x_j≠y_j\)

【输出】

输出一行一个整数,表示最少的花费。特别地,如果无法通过交换换取商品 \(b\) ,请输出 No solution

【输入样例】

3 5 0 2
1 2 4
1 0
2 0
0 1
2 1
1 2

【输出样例】

5

【算法标签】

《洛谷 P10110 商品交易》 #广度优先搜索BFS# #最短路# #GESP# #2023#

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 100005;  // 最大节点数
const int M = N * 2;   // 最大边数(无向图×2)
int n, m, a, b;        // n: 节点数, m: 边数, a: 起点, b: 终点
int v[N];              // 每个节点的值
int h[N], e[M], ne[M], w[M], idx;  // 邻接表
int dist[N];           // 最短距离
bool st[N];            // 节点是否在队列中

// 添加有向边
void add(int a, int b, int c)
{
    e[idx] = b;        // 边的终点
    w[idx] = c;        // 边的权重
    ne[idx] = h[a];    // 指向a的下一条边
    h[a] = idx++;      // 更新a的第一条边
}

// SPFA算法求最短路
void spfa()
{
    // 初始化距离为无穷大
    memset(dist, 0x3f, sizeof(dist));
    dist[a] = 0;  // 起点距离为0
    
    queue<int> q;   // SPFA队列
    q.push(a);      // 起点入队
    st[a] = true;   // 标记起点在队列中
    
    while (!q.empty())
    {
        int t = q.front();  // 取出队首节点
        q.pop();
        // cout << "t " << t << endl;  // 调试输出
        st[t] = false;  // 标记t不在队列中
        
        // 遍历t的所有邻接边
        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];  // 邻接节点
            // 松弛操作
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i] + 1;  // 更新距离
                if (!st[j])  // 如果j不在队列中
                {
                    q.push(j);   // j入队
                    st[j] = true;  // 标记j在队列中
                }
            }
        }
    }
    
    // 调试输出距离数组
    // for (int i=0; i<n; i++)
    //     cout << dist[i] << " ";
    // cout << endl;
}

int main()
{
    // 初始化邻接表
    memset(h, -1, sizeof(h));
    
    // 输入节点数、边数、起点、终点
    cin >> n >> m >> a >> b;
    
    // 输入每个节点的值
    for (int i = 0; i < n; i++)
    {
        cin >> v[i];
    }
    
    // 输入边并建图
    while (m--)
    {
        int x, y;
        cin >> x >> y;
        // 添加有向边,权重为v[y] - v[x]
        add(x, y, v[y] - v[x]);
    }
    
    // 运行SPFA算法
    spfa();
    
    // 输出结果
    if (dist[b] == 0x3f3f3f3f)  // 如果终点不可达
    {
        puts("No solution");
    }
    else
    {
        cout << dist[b] << endl;  // 输出最短距离
    }
    
    return 0;
}

【运行结果】

3 5 0 2
1 2 4
1 0
2 0
0 1
2 1
1 2
5

P10111 纸牌游戏

【题目来源】

洛谷:[P10111 GESP202312 七级] 纸牌游戏 - 洛谷

【题目描述】

你和小杨在玩一个纸牌游戏。

你和小杨各有 \(3\) 张牌,分别是 \(0、1、2\)。你们要进行 \(N\) 轮游戏,每轮游戏双方都要出一张牌,并按 \(1\) 战胜 \(0\)\(2\) 战胜 \(1\)\(0\) 战胜 \(2\) 的规则决出胜负。第 \(i\) 轮的胜者可以获得 \(2 \times a_i\) 分,败者不得分,如果双方出牌相同,则算平局,二人都可获得 \(a_i\)\((i=1,2,\cdots,N)\)

玩了一会后,你们觉得这样太过于单调,于是双方给自己制定了不同的新规则。小杨会在整局游戏开始前确定自己全部 \(n\) 轮的出牌,并将他的全部计划告诉你;而你从第 \(2\) 轮开始,要么继续出上一轮出的牌,要么记一次“换牌”。游戏结束时,你换了 \(t\) 次牌,就要额外扣 \(b_1+\cdots+b_t\) 分。

请计算出你最多能获得多少分。

【输入】

第一行一个整数 \(N\),表示游戏轮数。

第二行 \(N\) 个用单个空格隔开的非负整数 \(a_1,\cdots,a_N\),意义见题目描述。

第三行 \(N-1\) 个用单个空格隔开的非负整数 \(b_1,\cdots,b_{N-1}\),表示换牌的罚分,具体含义见题目描述。由于游戏进行 N 轮,所以你至多可以换 \(N-1\) 次牌。

第四行 \(N\) 个用单个空格隔开的整数 \(c_1,\cdots,c_N\),依次表示小杨从第 \(1\) 轮至第 \(N\) 轮出的牌。保证 \(c_i\in{0,1,2}\)

【输出】

一行一个整数,表示你最多获得的分数。

【输入样例】

4
1 2 10 100
1 100 1
1 1 2 0

【输出样例】

219

【算法标签】

《洛谷 P10111 纸牌游戏》 #动态规划DP# #GESP# #2023#

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 1005;  // 最大轮数
int n;               // 总轮数
int ans = -1e9;      // 最终答案
int a[N], b[N], c[N];  // a: 每轮基础得分, b: 换牌代价, c: 每轮出牌类型
int dp[N][N][3];     // dp[i][j][k]: 前i轮换了j次牌,第i轮出牌类型为k的最大得分

// 计算在第pos轮,上次出牌类型x,本次出牌类型y的得分
int calc(int x, int y, int pos)
{
    if (x == y)  // 两次出牌类型相同
    {
        return a[pos];  // 得a[pos]分
    }
    if (x == 0)  // 上次出石头
    {
        if (y == 1)  // 这次出布
        {
            return 0;  // 平局
        }
        else  // 这次出剪刀
        {
            return 2 * a[pos];  // 获胜
        }
    }
    else if (x == 1)  // 上次出布
    {
        if (y == 0)  // 这次出石头
        {
            return 2 * a[pos];  // 获胜
        }
        else  // 这次出剪刀
        {
            return 0;  // 平局
        }
    }
    else  // 上次出剪刀
    {
        if (y == 0)  // 这次出石头
        {
            return 0;  // 平局
        }
        else  // 这次出布
        {
            return 2 * a[pos];  // 获胜
        }
    }
}

int main()
{
    // 输入
    cin >> n;
    for (int i = 1; i <= n; i++)  // 每轮基础得分
    {
        cin >> a[i];
    }
    for (int i = 1; i < n; i++)  // 第i次换牌的代价
    {
        cin >> b[i];
    }
    for (int i = 1; i <= n; i++)  // 第i轮必须出的牌型
    {
        cin >> c[i];
    }
    
    // 初始化dp为极小值
    memset(dp, 0, sizeof(dp));  // 实际为0,但代码中未显示初始化
    
    // 动态规划
    for (int i = 1; i <= n; i++)  // 枚举轮数
    {
        for (int j = 0; j < i; j++)  // 枚举换牌次数
        {
            for (int k = 0; k <= 2; k++)  // 枚举当前轮实际出牌类型
            {
                // 计算当前轮得分
                int x = calc(k, c[i], i);
                // 不换牌的情况
                dp[i][j][k] = dp[i - 1][j][k] + x;
                
                // 如果j=0,不能换牌
                if (j == 0) continue;
                
                // 逻辑检查:第i轮最多换i-1次牌
                if (j == i - 1) 
                {
                    dp[i][j][k] = -1e9;  // 标记为不可能
                }
                
                // 换牌的情况
                if (k == 0)  // 当前出石头
                {
                    dp[i][j][k] = max(dp[i][j][k], 
                        max(dp[i - 1][j - 1][1], dp[i - 1][j - 1][2]) - b[j] + x);
                }
                else if (k == 1)  // 当前出布
                {
                    dp[i][j][k] = max(dp[i][j][k],
                        max(dp[i - 1][j - 1][0], dp[i - 1][j - 1][2]) - b[j] + x);
                }
                else  // 当前出剪刀
                {
                    dp[i][j][k] = max(dp[i][j][k],
                        max(dp[i - 1][j - 1][0], dp[i - 1][j - 1][1]) - b[j] + x);
                }
            }
        }
    }
    
    // 找最大值
    for (int i = 0; i < n; i++)
    {
        ans = max({ans, dp[n][i][0], dp[n][i][1], dp[n][i][2]});
    }
    
    // 输出结果
    cout << ans << endl;
    
    return 0;
}

【运行结果】

4
1 2 10 100
1 100 1
1 1 2 0
219
posted @ 2026-01-18 22:15  热爱编程的通信人  阅读(3)  评论(0)    收藏  举报