2025HAUE新生周赛二C++题解

2025HAUE新生周赛二C++题解

前言

本场题目相较第一场难度略有提升,为了让大部分人有良好的体验,我对题面进行了精心的加工(在一个月里进行了多次修改)。

不知道大家有没有感觉理解题意很轻松?小Q还是太善良了!

本场涉及算法:排序,前缀和,动态规划。

除F I两题较为简单,其他题解均使用了DeepSeek添加注释,方便大家理解。

希望大家都能够在本场比赛中有所收获~

A-E题设定来源:Steins;Gate;

G,H题设定来源:Rick and Morty;

都是相当有趣的作品,感兴趣的同学可以看一看~


难度预期

简单 F I A D

中等 E B H G

难 C J

题解顺序依照难度预期。


F 小Q的算法秘典

逐行复制粘贴输出即可~

#include <iostream>
using namespace std;
int main() {
    cout<<"+===========+"<<endl;
    cout<<"|###########|"<<endl;
    cout<<"|####+==+##|"<<endl;
    cout<<"|####|算| #|"<<endl;
    cout<<"|####|法| #|"<<endl;
    cout<<"|####|秘| #|"<<endl;
    cout<<"|####|典| #|"<<endl;
    cout<<"|####+==+##|"<<endl;
    cout<<"|###########|"<<endl;
    cout<<"|###########|"<<endl;
    cout<<"+===========+"<<endl;
}

这里再教大家一种特别的输出方法。

C++11引入的----原始字符串字面量(raw string literals)。这是一种增强型的字符串表示方式,用于简化处理复杂的字符串。

它与普通字符串字面量的主要区别在于,原始字符串字面量可以包含反斜杠(\)和引号(“)等特殊字符,而无需对他们进行转义。

格式为 R"(任意字符串)"。

例如:

#include<bits/stdc++.h>
using namespace std;
int main()
{
	string s=R"(printf("hello world!"))";
	cout<<s<<endl;
	cout<<R"(printf("hello world!"))";
}

输出

printf("hello world!")
printf("hello world!")

本题使用这个技巧,可以让你快人一步~

#include<bits/stdc++.h>
using namespace std;
int main()
{
	cout<<R"(+===========+
|###########|
|####+==+##|
|####|算| #|		
|####|法| #|
|####|秘| #|
|####|典| #|
|####+==+##|
|###########|
|###########|
+===========+)";
}

I 小Q的乒乓球对决 I

注意表中为小Q胜率,而需要输出的是Z老师的胜率

善良的小Q特意在题面中对此进行了加粗处理,仍然因此WA的同学以后要注意好好读题哦~

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int a,b;
    cin>>a>>b;
    if(a==1)
    {
        if(b==1)
            cout<<0.7;
        else
            cout<<0.5;
    }
    else
    {
        if(b)
            cout<<0.9;
        else
            cout<<0.8;
    }
}

A 小Q的心理相对论

版本一

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

int main() {
    int n;
    cin >> n;        // 读取活动数量
    
    double ans = 0;  // 记录实际感受总时长
    double sum = 0;  // 记录已记录活动的真实时长总和
    
    for(int i = 1; i <= n; i++) {
        double t, k;
        cin >> t >> k;      // 读取活动的真实时长t和心情系数k
        
        // 计算该活动的实际感受时长并累加
        ans += t * k;
        
        // 累加已记录活动的真实时长
        sum += t;
    }
    
    // 加上未记录时段的感受时长(心情系数为1,所以直接加时长)
    ans += (24 - sum);
    
    // 输出结果,保留2位小数
    cout << fixed << setprecision(2) << ans;
    
    return 0;
}

版本二

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

int main() {
    int n;
    cin >> n;           // 读取活动数量
    
    // 初始化总时长为24小时(假设所有时段心情系数都为1)
    double ans = 24;
    
    for(int i = 1; i <= n; i++) {
        double t, k;
        cin >> t >> k;  // 读取活动的真实时长t和心情系数k
        
        // 从总时长中减去该活动的原始时长(心情系数为1的情况)
        ans -= t;
        
        // 加上该活动的实际感受时长(考虑心情系数)
        ans += t * k;
    }
    
    // 输出结果,保留2位小数
    cout << fixed << setprecision(2) << ans;
    
    return 0;
}

D 小Q的时间跳跃机器

int类型的最大值为231-1,即2147483647。int类型的运算结果大于该值将导致数值溢出,结果错误。

所以要使用数据范围更大的long long进行运算。

long long类型的最大值263-1,即922337203685477。

善良的小Q已经在样例输出中给出了结果的最大值,仍然因此WA的同学要牢记此知识点~

记住:在竞赛中,当看到数据范围或结果可能较大时,优先使用 long long 可以避免很多不必要的错误!

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

int main() {
    int n;
    cin >> n;        // 读取时间跳跃次数n
    
    long long ans = 0;  // 使用long long防止整数溢出
    
    // 循环计算总疲劳值:1² + 2² + 3² + ... + n²
    for(int i = 1; i <= n; i++) {
        ans += i * i;  // 第i次跳跃产生i²的疲劳值
    }
    
    cout << ans;       // 输出总疲劳值
    return 0;
}

数学公式

更高效,不过在数据范围允许暴力求解时不需要浪费时间推导数学公式。

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

int main() {
    int n;
    cin >> n;
    long long ans = (long long)n * (n + 1) * (2 * n + 1) / 6;
    cout << ans;
    return 0;
}

E 小Q的时间机器

简单模拟。

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

int main() {
    int n, m;
    cin >> n >> m;  // 读取初始燃料量n和旅行次数m
    
    int x = 2025;   // 初始年份为2025年
    int cnt = 0;    // 记录总共需要消耗的燃料量
    
    // 遍历每次旅行
    for(int i = 1; i <= m; i++) {
        int y;
        cin >> y;           // 读取第i次旅行的目的地年份
        
        // 计算跨越年份所需的燃料:|目的地年份 - 出发年份|
        cnt += abs(y - x);
        
        // 更新当前位置为本次旅行的目的地
        x = y;
    }
    
    // 加上每次启动机器消耗的燃料:m次旅行 × 1单位/次
    cnt += m;
    
    // 判断燃料是否足够
    if(cnt > n)
        cout << "Need more!";   // 燃料不足
    else
        cout << "Yes sir!";     // 燃料足够
    
    return 0;
}

B 小Q的世界线变动率

整数位数分离。

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

int main() {
    int n;
    cin >> n;  // 读取数据条数
    int ans = 0;  // 记录干扰数据的数量
    
    for(int i = 1; i <= n; i++) {
        int a, b;      // a: 整数部分, b: 小数部分
        char op;       // 读取小数点 '.'
        
        // 格式化输入:整数部分 + 小数点 + 小数部分
        cin >> a >> op >> b;
        
        int d[6];  // 数组存储小数部分的6位数字
        
        // 将小数部分b的6位数字分解到数组d中
        // 从个位开始取,所以需要倒序存储
        for(int j = 5; j >= 0; j--) {
            d[j] = b % 10;  // 取最后一位数字
            b /= 10;        // 去掉最后一位
        }
        
        int o = 1;  // 标记是否为有效数据,1表示有效,0表示干扰
        
        // 检查小数部分的数字序列是否非递减
        for(int j = 0; j < 5; j++) {
            if(d[j] > d[j + 1]) {  // 如果前一位大于后一位
                o = 0;             // 标记为干扰数据
                break;             // 提前结束检查
            }
        }
        
        if(o == 0)  // 如果是干扰数据
            ans++;   // 计数器加1
    }
    
    // 输出结果
    if(ans == 0)
        cout << "El Psy Congroo!";  // 所有数据都有效
    else
        cout << ans;                // 输出干扰数据数量
    
    return 0;
}

使用字符串会简单许多~

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

int main() {
    int n;
    cin >> n;    // 读取数据条数
    int ans = 0; // 记录干扰数据的数量
    
    while(n--) {  // 循环处理每条数据
        string s;
        cin >> s;  // 以字符串形式读取整个浮点数
        
        int o = 1;  // 标记是否为有效数据,1表示有效,0表示干扰
        
        // 检查小数部分的数字序列是否非递减
        // 字符串格式:X.abcdef,小数部分从索引3开始
        for(int i = 3; i < s.size(); i++) {
            if(s[i] < s[i - 1]) {  // 如果当前数字小于前一个数字
                o = 0;             // 标记为干扰数据
                break;             // 提前结束检查
            }
        }
        
        if(o == 0)  // 如果是干扰数据
            ans++;   // 计数器加1
    }
    
    // 输出结果
    if(ans == 0)
        cout << "El Psy Congroo!";  // 所有数据都有效
    else
        cout << ans;                // 输出干扰数据数量
    
    return 0;
}

H 小Q的超级煤采集

升序排序后取后k个即可。

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n;
    cin >> n;
    int a[100010];  // 定义数组存储超级煤储量
    
    // 读取每个宇宙的超级煤储量
    for(int i = 0; i < n; i++)
        cin >> a[i];
    
    // 将数组从小到大排序
    sort(a, a + n);
    
    // 计算最多可以采集的宇宙数量k = ⌈n/2⌉
    int k = ceil(n * 1.0 / 2);
    
    int ans = 0;
    // 选择最大的k个值(排序后数组末尾的k个元素)
    for(int i = n - k; i < n; i++)
        ans += a[i];
    
    cout << ans;
    return 0;
}

也可降序排序后取前k个。

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

int main()
{
    int n;
    cin >> n;  // 读取平行宇宙的数量
    
    int a[100010];  // 数组存储每个宇宙的超级煤储量
    
    // 读取每个宇宙的超级煤储量
    for(int i = 0; i < n; i++)
        cin >> a[i];
    
    // 将超级煤储量按从大到小排序
    // greater<int>() 表示降序排列
    sort(a, a + n, greater<int>());
    
    // 计算最多可以停留采集的宇宙数量
    // k = ⌈n/2⌉ (n/2的上取整)
    int k = ceil(n * 1.0 / 2);
    
    int ans = 0;  // 记录最大采集量
    
    // 选择前k个最大的超级煤储量相加
    // 因为数组已按从大到小排序,前k个就是最大的k个值
    for(int i = 0; i < k; i++)
        ans += a[i];
    
    // 输出最大可能的采集量
    cout << ans;
    
    return 0;
}

冒泡排序

sort函数的时间复杂度为O(n log n),冒泡排序的时间复杂度为O(n2)。在本题中如果n为10 000以上,冒泡排序将有时间超限的风险。

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n;
    cin >> n;
    int a[100010];
    
    for(int i = 0; i < n; i++)
        cin >> a[i];
    
    // 使用冒泡排序实现从大到小排序
    for(int i = 0; i < n - 1; i++)
        for(int j = 0; j < n - 1 - i; j++)
            if(a[j] < a[j + 1])  // 如果前一个小于后一个,交换位置
                swap(a[j], a[j + 1]);
    
    // 计算最多可以采集的宇宙数量k = ⌈n/2⌉
    int k = ceil(n * 1.0 / 2);
    
    int ans = 0;
    // 选择最大的k个值(排序后数组前k个元素)
    for(int i = 0; i < k; i++)
        ans += a[i];
    
    cout << ans;
    return 0;
}

G 小Q的中央有限曲线

前缀和模板题。

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

// 定义数组:a存储原始数据,s存储前缀和
int a[100010], s[100010];

int main()
{
    int n, m;
    cin >> n >> m;  // 读取宇宙数量n和查询次数m
    
    // 读取每个宇宙的稳定性数值,从下标1开始存储
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    
    // 计算前缀和数组
    // s[i]表示前i个宇宙的稳定性数值之和
    // 即:s[i] = a[1] + a[2] + ... + a[i]
    for(int i = 1; i <= n; i++)
        s[i] = s[i-1] + a[i];  // 当前前缀和 = 前一个前缀和 + 当前数值
    
    // 处理m次查询
    while(m--)
    {
        int l, r;
        cin >> l >> r;  // 读取查询区间[l, r]
        
        // 利用前缀和计算区间和:s[r] - s[l-1]
        // s[r] = a[1] + a[2] + ... + a[r]
        // s[l-1] = a[1] + a[2] + ... + a[l-1]
        // 相减得到:a[l] + a[l+1] + ... + a[r]
        cout << s[r] - s[l-1] << '\n';  // 使用'\n'提高输出效率
    }
    
    return 0;
}

C 小Q的D-Mail

较难的字符串处理。

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

int main()
{
    string s;
    cin >> s;  // 读取原始字符串
    
    // 技巧:在字符串末尾添加一个不会出现的字符(大写'A')
    // 这样可以确保最后一个字符序列也能被正确处理
    s += "A";
    
    int o = 1;  // 计数器,记录当前字符连续出现的次数,初始为1
    string s1 = "";  // 存储压缩后的结果字符串
    
    // 遍历字符串(从第2个字符开始到最后一个添加的字符)
    for(int i = 1; i < s.size(); i++)
    {
        if(s[i] == s[i-1])  // 当前字符与前一个字符相同
            o++;            // 增加计数器
        else
        {
            // 遇到不同字符,处理前一个字符序列
            if(o != 1)  // 如果连续出现次数大于1
            {
                s1 += s[i-1];           // 添加字符本身
                s1 += to_string(o);     // 添加出现次数
            }
            else  // 只出现1次
                s1 += s[i-1];           // 只添加字符本身
            
            o = 1;  // 重置计数器为1(当前新字符)
        }
    }
    
    // 检查压缩后长度是否超过36字节
    if(s1.size() > 36)
        cout << "D-Mail too long!";
    else
        cout << s1;
    
    return 0;
}

J 小Q的乒乓球对决 II

作为本场的防AK题,应该看两眼题面就被劝退了吧?

被劝退是对的!如果优先死磕难题,不仅会浪费大量时间,还容易心态爆炸。

动态规划题目看题解不会很难理解,不过上手写的话会相当有难度。

感兴趣的同学可以参悟下面两份不同版本题解后尝试自己写一下~

来自hyc学长的动态规划代码。

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

int x, y;
double pa, pb, ans;
double dp[30][30]; // dp[i][j]表示比分i:j时的概率

/**
 * 处理平局阶段(10:10以后)
 * @param a 小Q当前得分
 * @param b Z老师当前得分  
 * @param c 起始发球方:1-小Q发球,0-Z老师发球
 */
void OT(int a, int b, int c) {
    int sum = a + b; // 当前总得分
    
    // 遍历所有可能的比分
    for (int i = a; i < 20; i++) {
        for (int j = max(b, i - 1); j <= i + 1; j++) {
            // 计算当前发球方:平局阶段每球换发
            int k = (i + j - sum + c) % 2; // k=1:小Q发球, k=0:Z老师发球
            
            if (k) { // 小Q发球
                dp[i + 1][j] += dp[i][j] * pa;        // 小Q得分
                dp[i][j + 1] += dp[i][j] * (1 - pa);  // Z老师得分
            } else { // Z老师发球
                dp[i + 1][j] += dp[i][j] * (1 - pb);  // 小Q得分
                dp[i][j + 1] += dp[i][j] * pb;        // Z老师得分
            }
        }
    }
    
    // 累加小Q获胜的概率
    for (int i = 12; i <= 20; i++) {
        ans += dp[i][i - 2]; // 领先2分获胜(12:10, 13:11, ..., 20:18)
    }
    ans += dp[20][19]; // 特殊规则:19:19时先到20分获胜
}

/**
 * 处理常规阶段(10:10之前)
 * @param a 小Q起始得分
 * @param b Z老师起始得分
 */
void game(int a, int b) {
    int sum = a + b; // 当前总得分

    // 计算发球模式:常规阶段每人连续发2球
    // q和p表示小Q发球的轮次(在4球循环中的位置)
    int q = 0, p = 1; // 正常情况下小Q发第1、2球
    if (sum % 2) p = 3; // 如果总得分奇数,调整发球模式

    // 遍历常规阶段所有可能比分
    for (int i = a; i < 11; i++) {
        for (int j = b; j < 11; j++) {
            int k = (i + j - sum) % 4; // 计算当前在4球循环中的位置
            
            if (k == q || k == p) { // 小Q发球轮次
                dp[i + 1][j] += dp[i][j] * pa;        // 小Q得分
                dp[i][j + 1] += dp[i][j] * (1 - pa);  // Z老师得分
            } else { // Z老师发球轮次
                dp[i + 1][j] += dp[i][j] * (1 - pb);  // 小Q得分  
                dp[i][j + 1] += dp[i][j] * pb;        // Z老师得分
            }
        }
    }

    // 累加小Q在常规阶段获胜的概率(11:0 到 11:9)
    for (int i = 0; i <= 9; i++) 
        ans += dp[11][i];

    // 清除10:11和11:10的概率(这些比分不会在常规阶段出现)
    dp[11][10] = 0; 
    dp[10][11] = 0;

    // 计算进入平局阶段时的起始发球方
    int k = (10 + 10 - sum) % 4; // 10:10时的发球轮次
    int c = 0; // 平局阶段起始发球方
    if (k == q || k == p) c = 1; // 如果10:10时是小Q发球,则平局阶段从小Q开始

    // 处理平局阶段
    OT(10, 10, c);
}

void solve() {
    cin >> x >> y >> pa >> pb;

    dp[x][y] = 1; // 初始化:当前比分的概率为1

    // 根据当前比分选择处理方式
    if (x < 10 && y < 10) 
        game(x, y);  // 常规阶段
    else 
        OT(x, y, 1); // 平局阶段,假设从小Q发球开始
    
    cout << fixed << setprecision(6) << ans << endl;
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    solve();
    return 0;
}

记忆化搜索。(在赛时将时间限制设为2s,即使不做记忆化,常规DFS也不会时间超限)

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

// 记忆化数组:m[i][j][r][t] 表示当前比分i:j,发球方r,当前发球轮次t时小Q获胜的概率
double m[25][25][2][2];
double Pa, Pb; // 小Q发球得分概率,Z老师发球得分概率

/**
 * 递归计算小Q获胜概率
 * @param i 小Q当前得分
 * @param j Z老师当前得分
 * @param r 当前发球方:0-小Q发球,1-Z老师发球
 * @param t 当前发球轮次:0-第1个发球,1-第2个发球(仅在常规阶段有效)
 * @return 小Q获胜的概率
 */
double dfs(int i, int j, int r, int t) {
    // 边界条件:根据特殊规则,先到20分者获胜
    if (i >= 20) return 1.0;  // 小Q达到20分获胜
    if (j >= 20) return 0.0;  // Z老师达到20分获胜
    
    // 常规胜利条件:先到11分且领先至少2分
    if (i >= 11 && i - j >= 2) return 1.0;  // 小Q常规胜利
    if (j >= 11 && j - i >= 2) return 0.0;  // Z老师常规胜利
    
    // 记忆化:如果已经计算过,直接返回结果
    if (m[i][j][r][t] >= 0) return m[i][j][r][t];
    
    double res = 0;
    
    // 平局阶段(双方都达到10分以上)
    if (i >= 10 && j >= 10) {
        if (r == 0) {  // 小Q发球
            // 平局阶段每球换发:赢球得分且换发,输球对方得分且换发
            res = Pa * dfs(i + 1, j, 1, 0) + (1 - Pa) * dfs(i, j + 1, 1, 0);
        } else {  // Z老师发球
            res = (1 - Pb) * dfs(i + 1, j, 0, 0) + Pb * dfs(i, j + 1, 0, 0);
        }
    } 
    // 常规阶段
    else {
        if (t == 0) {  // 当前是第1个发球
            if (r == 0) {  // 小Q发球
                // 赢球或输球后,仍由小Q发球(第2个发球)
                res = Pa * dfs(i + 1, j, 0, 1) + (1 - Pa) * dfs(i, j + 1, 0, 1);
            } else {  // Z老师发球
                res = (1 - Pb) * dfs(i + 1, j, 1, 1) + Pb * dfs(i, j + 1, 1, 1);
            }
        } else {  // 当前是第2个发球
            if (r == 0) {  // 小Q发球
                // 第2个发球结束后,换Z老师发球
                res = Pa * dfs(i + 1, j, 1, 0) + (1 - Pa) * dfs(i, j + 1, 1, 0);
            } else {  // Z老师发球
                // 第2个发球结束后,换小Q发球
                res = (1 - Pb) * dfs(i + 1, j, 0, 0) + Pb * dfs(i, j + 1, 0, 0);
            }
        }
    }
    
    // 保存结果到记忆化数组
    m[i][j][r][t] = res;
    return res;
}

int main() {
    int x, y;
    cin >> x >> y;  // 读取当前比分
    cin >> Pa >> Pb; // 读取得分概率
    
    // 初始化记忆化数组为-1,表示未计算
    for (int i = 0; i < 25; i++) {
        for (int j = 0; j < 25; j++) {
            for (int k = 0; k < 2; k++) {
                for (int l = 0; l < 2; l++) {
                    m[i][j][k][l] = -1;
                }
            }
        }
    }
    
    double ans;
    // 根据当前比分确定初始状态
    if (x >= 10 && y >= 10) {
        // 平局阶段:初始发球方为小Q(0),轮次参数设为0(在平局阶段轮次参数无意义)
        ans = dfs(x, y, 0, 0);
    } else {
        // 常规阶段:根据总得分判断是小Q的第几个发球
        if ((x + y) & 1) {  // 总得分为奇数,是小Q的第2个发球
            ans = dfs(x, y, 0, 1);
        } else {  // 总得分为偶数,是小Q的第1个发球
            ans = dfs(x, y, 0, 0);
        }
    }
    
    // 输出结果,保留6位小数
    cout << fixed << setprecision(6) << ans << endl;
    return 0;
}

赛时随笔

观察到有人用long (int)通过了D题。

在C语言中,long 类型的字节长度并不是固定的,它会根据不同的操作系统和平台有所变化。一般来说,在32位系统中,long 通常占用 4个字节,而在64位系统中,long 可能占用 8个字节。

机房电脑是32位系统,long占4个字节,与int大小相同,所以在机房电脑的DevC++中样例3000的结果无法正确输出。

而OJ服务器是64位系统,long占8个字节,与long long大小相同,可以通过题目。


C题本来是要当作防AK,念起今年同学们的代码能力较强,即增添了J题作防AK。结果不出所料,C题能过3个人(其余两个是打星参赛)。


在难度预期里前缀和模板题G题是比C题等级低的,但没想到大家都还没学前缀和…


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值