2023 中国大学生程序设计竞赛(CCPC)新疆赛区(重现赛)部分题解分析+AC代码

补题链接:2023 中国大学生程序设计竞赛(CCPC)新疆赛区(重现赛)

H:数学

我第一题做出来的是H题

和前一天做的一道四平方和有点像,但这个要求是统计所有的,第一想法想到了dp然后过了。

这个问题要求找到一个正整数 n 的每个正整数 i 的最少正整数平方和分解个数f\left ( i \right ),然后计算\prod 1\leq i\leq f\left ( i \right )mod \cdot 998244353 。特别的 f\left ( x^{2} \right )= 1

解题思路

  1. 初始化

    • 创建 dp数组,大小为 1e5 + 5,用于存储从 1n的每个正整数的最少平方和分解个数。初始时,将所有 dp元素设为 n。
    • 设置 dp[0] = 0,因为 0 的平方和个数为 0;设置 dp[1] = 1,因为 1 的平方和个数为 1 (即 1^{2})。
    • 创建 ans数组,大小为 1e5 + 5,用于存储所有小于等于 1e5 的平方数。
  2. 预处理平方数

    • 遍历所有可能的整数 j,计算其平方并存入 ans 数组,以便后续使用。
  3. 动态规划填表

    • 对于每个正整数 i ,尝试所有小于等于 i 的平方数 j^{2},更新 dp[i]dp[i - j^2] + 1 中的最小值,确保得到最小的平方和分解个数。
  4. 计算最终结果

    • 初始化 cnt为 1,表示乘积的初始值。
    • 遍历 dp数组从 1 到 n,将每个 dp[i] 乘到 cnt上并取模 998244353。

方法分析

  • 输入处理:读取输入的正整数 n。
  • 初始化:设置 dp 数组初始值为 n,dp[0] = 0dp[1] = 1分别初始化为 0 和 1。
  • 预处理平方数:预计算所有小于等于 1e5 的平方数存入 ans 数组,避免后续重复计算,提高效率。
  • 动态规划:通过双重循环更新 dp 数组,每次尝试所有可能的平方数并更新最小分解数,确保 dp[i] 存储最小的平方和分解个数。
  • 取模运算:在计算结果时对每一步都进行取模操作,防止数据溢出,并满足题目要求。

#include <bits/stdc++.h>
#define simeple freopen("input", "r", stdin),freopen("output", "w", stdout);
#define fast ios::sync_with_stdio(false),cin.tie(0);cout.tie(0);
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int MOD = 998244353;

int main() {
    fast
    // simeple

    int n;
    cin >> n;
    
    vector<int> dp(1e5 + 5);  // dp 数组,用于存储每个数的最少平方和分解个数
    vector<int> ans(1e5 + 5);  // ans 数组,用于存储所有小于等于 1e5 的平方数

    // 初始化 dp 数组的值为 n,表示初始时的最大分解个数
    for (int i = 1; i <= n; i++) {
        dp[i] = n;
    }
    
    dp[1] = 1;  // 1 可以用 1 个平方数表示
    dp[0] = 0;  // 0 可以用 0 个平方数表示

    // 预处理所有小于等于 1e5 的平方数
    for (int i = 1; i * i <= 1e5; i++) {
        ans[i] = i * i;
    }

    // 动态规划计算每个数的最少平方和分解个数
    for (int i = 2; i <= n; i++) {
        for (int j = 1; j * j <= 1e5; j++) {
            if (i >= ans[j]) {
                dp[i] = min(dp[i - ans[j]] + 1, dp[i]);  // 更新 dp[i] 的值为最小分解数
            } else {
                break;  // 如果平方数大于 i,则退出循环
            }
        }
    }

    long long cnt = 1;  // 乘积初始值为 1
    // 计算最终结果,将所有 dp[i] 的乘积取模
    for (int i = 1; i <= n; i++) {
        cnt = cnt * dp[i] % MOD;
    }
    cout << cnt;
    return 0;
}

D:在此同步

第二题做出来的是D题,个人感觉算一个思维题。

题目要求我们找到一个与给定排列 p 长度相同且同步值(即逆序对数)也相同的排列。我们需要通过适当的交换使得新的排列与原排列的同步值一致。

解题思路

  1. 输入处理

    • 读取输入的正整数 n
    • 读取长度为 n的排列 p
  2. 寻找符合条件的位置进行交换

    • 遍历数组中的每个位置 i (从 1n-1),检查 p[i] 是否符合一个峰值或谷值的条件,即满足 p[i] > p[i+1]&&p[i] > p[i-1] 或 p[i] < p[i+1]&&p[i] < p[i-1]
    • 若找到满足条件的位置,判断其前后的元素是否能进行交换,从而不改变逆序对数。
    • 根据元素的大小关系决定如何交换,确保交换后新排列的逆序对数与原排列一致。
  3. 输出结果

    • 若找到合适的交换位置并完成交换,则输出新的排列。
    • 若遍历完所有元素未找到合适的交换位置,则输出 -1 表示无法找到符合条件的排列。

方法分析

  • 输入处理:读取输入的正整数 n及长度为 n的排列 p
  • 遍历寻找交换位置:通过检查每个位置 i是否为峰值或谷值,找到合适的交换位置。
  • 条件交换:根据相邻元素的大小关系决定如何交换,确保交换后新的排列逆序对数与原排列一致。
  • 输出结果:若找到合适的位置进行交换,则输出新的排列;否则输出 -1 表示无法找到符合条件的排列。

#include <bits/stdc++.h>
#define simple freopen("input", "r", stdin),freopen("output", "w", stdout);
#define fast ios::sync_with_stdio(false),cin.tie(0);cout.tie(0);
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

int main() {
    fast
    //simple
    int n;
    cin >> n;
    vector<int> ans(n);
    for (int i = 0; i < n; i++) {
        cin >> ans[i];
    }
    // 遍历寻找合适的交换位置
    for (int i = 1; i < n - 1; i++) {
        // 检查是否为峰值或谷值
        if ((ans[i] > ans[i + 1] && ans[i] > ans[i - 1]) || (ans[i] < ans[i + 1] && ans[i] < ans[i - 1])) {
            // 判断前后元素是否能进行交换
            if (ans[i - 1] < max(ans[i + 1], ans[i]) && ans[i - 1] > min(ans[i], ans[i + 1])) {
                swap(ans[i - 1], ans[i + 1]);  // 进行交换
                swap(ans[i - 1], ans[i]);  // 调整元素顺序
            } else {
                swap(ans[i + 1], ans[i - 1]);  // 进行交换
                swap(ans[i], ans[i + 1]);  // 调整元素顺序
            }
            for (int j = 0; j < n; j++) {
                cout << ans[j] << " ";
            }
            return 0;  // 结束程序
        }
    }
    // 若未找到合适的交换位置,输出 -1
    cout << -1;
    return 0;
}

剩下的补完题一起写。

  • 12
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值