1

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

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

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

适合人群:

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

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


编程题

P10723 黑白翻转

【题目来源】

洛谷:[P10723 GESP202406 七级] 黑白翻转 - 洛谷

【题目描述】

小杨有一棵包含 \(n\) 个节点的树,这棵树上的任意一个节点要么是白色,要么是黑色。小杨认为一棵树是美丽树当且仅当在删除所有白色节点之后,剩余节点仍然组成一棵树。

小杨每次操作可以选择一个白色节点将它的颜色变为黑色,他想知道自己最少要执行多少次操作可以使得这棵树变为美丽树。

【输入】

第一行包含一个正整数 \(n\),代表树的节点数。

第二行包含 \(n\) 个非负整数 \(a_1,a_2,\ldots,a_n\),其中如果 \(a_i=0\),则节点 \(i\) 的颜色为白色,否则为黑色。

之后 \(n-1\) 行,每行包含两个正整数 \(x_i,y_i\),代表存在一条连接节点 \(x_i\)\(y_i\) 的边。

【输出】

输出一个整数,代表最少执行的操作次数。

【输入样例】

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

【输出样例】

2

【算法标签】

《洛谷 P10723 黑白翻转》 #树形DP# #拓扑排序# #树的遍历# #GESP# #2024#

【代码详解】

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

const int N = 100005, M = N * 2;  // M是边数的两倍,因为是无向图
int n, ans;                       // n: 节点数, ans: 答案
int a[N];                         // a[i]: 节点i的状态,0表示白色,1表示黑色
int h[N], e[M], ne[M], idx;       // 邻接表存储树结构

// 添加无向边
void add(int a, int b)
{
    e[idx] = b;          // 存储边的终点
    ne[idx] = h[a];      // 将新边插入链表头部
    h[a] = idx;          // 更新头指针
    idx++;               // 边编号自增
}

// 深度优先搜索
// u: 当前节点
// fa: 父节点,防止走回头路
void dfs(int u, int fa)
{
    int s = 0;  // 统计子节点中黑色节点的数量
    
    // 遍历当前节点的所有邻居
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];  // 邻居节点
        if (j == fa)   // 如果是父节点,跳过
        {
            continue;
        }
        
        // 递归处理子树
        dfs(j, u);
        
        // 累加子节点的颜色
        s += a[j];
    }
    
    // 关键条件判断:
    // 1. s > 0: 当前节点至少有一个黑色子节点
    // 2. a[u] == 0: 当前节点是白色
    if (s && !a[u])
    {
        ans++;      // 答案加1
        a[u] = 1;   // 将当前节点染成黑色
    }
}

int main()
{
    // 输入节点数
    cin >> n;
    
    // 初始化邻接表
    memset(h, -1, sizeof(h));
    
    // 输入每个节点的初始颜色
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    
    // 输入n-1条边,构建树
    for (int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        add(u, v);  // 添加无向边
        add(v, u);
    }
    
    // 从任意一个黑色节点开始DFS
    for (int i = 1; i <= n; i++)
    {
        if (a[i] == 1)  // 找到第一个黑色节点
        {
            dfs(i, 0);  // 从该节点开始DFS,父节点为0
            break;
        }
    }
    
    // 输出答案
    cout << ans << endl;
    
    return 0;
}

【运行结果】

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

P10724 区间乘积

【题目来源】

洛谷:[P10724 GESP202406 七级] 区间乘积 - 洛谷

【题目描述】

小杨有一个包含 \(n\) 个正整数的序列 \(A=[a_1,a_2,\dots,a_n]\)

小杨想知道有多少对 \(<l,r>(1\le l\le r\le n)\) 满足 \(a_l\times a_{l+1}\times \dots \times a_r\) 为完全平方数。

一个正整数 \(x\) 为完全平方数当且仅当存在一个正整数 \(y\) 使得 \(x=y\times y\)

【输入】

第一行包含一个正整数 \(n\),代表正整数个数。

第二行包含 \(n\) 个正整数 \(a_1,a_2,\dots,a_n\),代表序列 \(A\)

【输出】

输出一个整数,代表满足要求的 \(<l,r>\) 数量。

【输入样例】

5
3 2 4 3 2

【输出样例】

2

【算法标签】

《洛谷 P10724 区间乘积》 #数论# #前缀和# #位运算# #GESP# #2024#

【代码详解】

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

#define int long long  // 定义int为long long类型

int n, ans;                     // n: 数组长度, ans: 答案
int a[100005][12];              // a[i][0]存储原数,a[i][1..11]存储质因数分解结果
int b[12] = {0, 0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29};  // 前11个质数(从索引1开始)

// 计算第k个数的质因数分解,并累加到前缀和中
void calc(int k)
{
    int x = a[k][0];  // 获取第k个数的原始值
    
    // 将前一个数的质因数分解结果复制到当前数
    for (int i = 1; i <= 11; i++)
    {
        a[k][i] = a[k-1][i];
    }
    
    // 特殊处理x=1的情况
    if (x == 1)
    {
        a[k][1] += 2;  // 1的特殊处理,将质数2的指数加2
    }
    
    // 对x进行质因数分解
    for (int i = 2; i <= 11; i++)
    {
        // 不断除以质数b[i],直到不能整除为止
        while (x % b[i] == 0)
        {
            x /= b[i];      // 除以质因数
            a[k][i]++;      // 对应质因数的指数加1
        }
    }
}

signed main()  // 由于#define int long long,所以用signed main
{
    cin >> n;  // 输入数组长度
    
    // 读取n个数并计算每个数的质因数分解前缀和
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i][0];  // 将输入的数存储在a[i][0]
        calc(i);         // 计算第i个数的质因数分解
    }
    
    // 统计满足条件的子数组数量
    for (int i = 1; i <= n; i++)      // 子数组的右端点
    {
        for (int j = 0; j <= i; j++)  // 子数组的左端点-1(j=0表示从开始)
        {
            int f = 0, s = 0;  // f: 是否不满足条件标志, s: 总指数和
            
            // 检查子数组a[j+1..i]是否满足条件
            for (int k = 1; k <= 11; k++)
            {
                // 计算子数组中第k个质因数的总指数
                s += a[i][k] - a[j][k];
                
                // 检查这个质因数的指数是否为奇数
                if ((a[i][k] - a[j][k]) % 2)
                {
                    f = 1;  // 存在指数为奇数的质因数
                    break;
                }
            }
            
            // 如果所有质因数的指数都是偶数,且总指数和不为0
            if (!f && s)
            {
                ans++;  // 计数加1
            }
        }
    }
    
    cout << ans << endl;  // 输出结果
    
    return 0;
}
// 100分
#include <bits/stdc++.h>
using namespace std;

#define int long long  // 将int定义为long long类型

int n, x, y, ans;                     // n: 数字个数, x: 前缀异或和, y: 临时变量, ans: 答案
int a[1 << 11] = {1};                 // a[state]: 状态为state的前缀数量,初始a[0]=1
int b[11] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29};  // 前10个质数(索引1-10)

// 计算函数:返回y的质因数分解状态的二进制表示
int calc()
{
    if (y == 1)  // 如果y等于1,返回0
    {
        return 0;
    }
    
    int s = 0;  // 状态值,用二进制表示
    
    // 遍历前10个质数
    for (int i = 1; i <= 10; i++)
    {
        int t = 0, c = 0;  // t: 当前质数的状态位, c: 当前质数的指数计数
        
        // 对y进行质因数分解
        while (y % b[i] == 0)
        {
            y /= b[i];  // 除以质因数
            c++;         // 指数加1
            
            // 关键:只关心指数的奇偶性
            // 如果c是奇数,设置对应的状态位
            if (c % 2)
            {
                t = 1 << i;  // 第i位设为1
            }
            else
            {
                t = 0;  // 第i位设为0
            }
        }
        
        s += t;  // 累加状态
    }
    
    return s;  // 返回状态值
}

signed main()  // 由于使用了#define int long long,所以main要改为signed
{
    // 输入数字个数
    cin >> n;
    
    // 处理n个数字
    for (int i = 1; i <= n; i++)
    {
        // 输入当前数字
        cin >> y;
        
        // 计算:y = 输入的数字 ⊕ calc()的返回值
        // 注意:这里y既用作输入,又在calc()中被修改
        y = x ^ calc();  // x是之前的前缀异或和
        
        // 统计答案:当前状态y出现的次数
        ans += a[y]++;  // 加上之前出现过的次数,然后计数加1
        
        // 更新前缀异或和
        x = y;
    }
    
    // 输出结果
    cout << ans << endl;
    
    return 0;
}

【运行结果】

5
3 2 4 3 2
2
posted @ 2026-01-18 08:56  热爱编程的通信人  阅读(2)  评论(0)    收藏  举报