可达鸭CSP-J复赛模拟day1赛后补题报告

日期:2023年10月2日星期一

S08695韩润琦

目录

1.比赛概况

2.比赛过程

3.题解报告

(1)数字降级

思路

STD

(2)分组

思路

STD

(3)抢夺地盘

思路

STD

(4)闯关

思路1

STD1

思路2

STD2

  1. 比赛概况

比赛共有四道题,满分400,赛时拿到50分。其中第一题50分,其余全部0分。

  1. 比赛过程

比赛开始时先把题目大体看了一遍,发现第一题是最简单的,于是就从T1开始做。

T1做题过程:这道题目其实完全是想复杂了, 我先是发现所有的偶数的情况答案一定都为1,因为它一定可以整除2,而2又是一个素数,所以我先写了一个判断偶数的if,然后直接输出1,再return 0就可以了(其实,根据唯一分解定理,所有的合数都满足此情况,直接判断哪些是素数就可以了)。接着我便开始处理奇数的情况,我先是写了一个for遍历每个素数,然后每个去除n,接着我就发现了一个问题,就是:有两种情况:(1)我用除出来的这个数再接着往下遍历计算,可是i的值正在随着for变化,

我无法判断是继续往下除或是始终用这个i来除,抑或是舍弃这个商,用原来的n来除下一个i。所以我拿了两个数组来存我的这些数据,最后来遍历取min。

总而言之,我写了一堆废代码,一道18行就能AC的题目我写了70行,还WA了。

T2做题过程:情况和T1很像,也是写了一大堆无用的代码。我是先把输入进去的数组sort了一下,然后就那首元素(也就是最小的那个)去往后找递增序列。

于是,我拿了一个find函数,去二分查找去找下一个元素。我还拿了一个flag的数组准备用它来记录是否还递增以及其不再递增和位置,然后拿到a数组里去取内容,

再用sum累加。我还写了“zhaoshu”和“minn”两个函数来查找下一次开始的位置。但测试的时候我无论输入什么数据都没有输出,于是此题没有成绩。

T3做题过程: 这一道题目大眼看过去很明显是一道DP的题目,而且是熟悉的线性DP的合唱队列问题,但因为忘了线性DP的模板所以此题也是0分。以后因该加强背诵。

T4做题过程:此题因为时间原因,没有完成所以也是0分。

  1. 题解报告
  1. 第一题:数字降级

情况:赛中50分,已补题。

题意:题目很简单,就是求一个数需要除几次,才能变成素数。

赛时本题想法:根据唯一分解定理,可以知道任意一个大于等于 的数字,都可以找到另一个数字将它除成质数。因此可以直接判断输入的数字是否为质数,如果为质数输出 0 ,否则输出 1 。

直接给出AC代码

代码:

#include <algorithm>

#include <cmath>

#include <cstdio>

#include <cstring>

#include <iostream>

using namespace std;

int main() {

    ll n;

    cin >> n;

    for (int i = 2; i <= n / i; i++) {

        if (n % i == 0) {

            cout << 1 << endl;

            return 0;

        }

    }

    cout << 0 << endl;

    return 0;

}

  1. 第二题:分组

情况:赛中0分,已补题。

题意: 寻找上升序列,然后找到不上升的位置,那最小的节点,最后把最大值相加。

赛时本题想法:已在比赛过程中详细说明,再次不做过多赘述。

题解:考虑有多少个小组分数可以至少为 1,这些小组中必须有一个0 。

所以假设 0有 x个,那么就说明有x 个小组分数至少为1 ,此时答案增加 x(这一步是考虑这些小组对答案的贡献)。

再考虑有多少个小组分数可以至少为2 ——这些小组必须有一个1 ,并且小组分数至少为2 的小组的数量一定小于等于小组分数至少为 1的小组数量,所以涉及到一个求 min 的操作。

直接给出AC代码

代码:

#include <algorithm>

#include <cmath>

#include <cstdio>

#include <cstring>

#include <iostream>

#include <map>

#include <queue>

#include <set>

#include <stack>

#include <string>

#include <vector>

using namespace std;

#define TIE ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);

typedef pair<int, int> pii;

typedef long long ll;

typedef unsigned long long ull;

const int N = 1e5 + 10, M = 1e5 + 10;

const double PI = acos(-1.0);

const double eps = 1e-6;

const ll mod = 1e9 + 7;

const int INF = 0x3f3f3f3f;

int a, cnt[N];

int main() {

    int n;

    cin >> n;

    memset(cnt, 0, sizeof cnt);

    for (int i = 1; i <= n; i++) {

        cin >> a;

        cnt[a]++;

    }

    int now = cnt[0], ans = now;

    for (int i = 1; i <= 1000; i++) {

        now = min(now, cnt[i]);

        ans += now;

    }

    cout << ans << endl;

    return 0;

}

(3)第三题:抢夺地盘

情况:赛中0分,已补题。

题意:求两次最长上升子序列,和《合唱队列》很像,有n个城镇,实现前p个为不下降的序列,后面p到n为不上升的序列,需要多少次调换。

赛时本题想法:已在比赛过程中详细说明,再次不做过多赘述。

题解:比较模板的 LIS 问题,从 到 求一次最长上升子序列,然后从 到 求一边最长下降子序列。注意这样的话会有一个重复点 ,所以考虑 是否需要修改。然后分类讨论。

直接给出AC代码

代码:

#include <iostream>

using namespace std;

const int maxn = 1e5 + 5;

int a[maxn], tp[maxn];

int main() {

    int n, pos, p = 1, ans = 0;

    cin >> n >> pos;

    for(int i = 1; i <= n; i++) {

        cin >> a[i];

    }

    tp[1] = a[1];

    for(int i = 2; i < pos; i++) {

        if(a[i] >= tp[p]) tp[++p] = a[i];

        else {

            int l = 1, r = p, mid = p >> 1;

            while(l != r) {

                if(a[i] < tp[mid]) r = mid;

                else l = mid + 1;

                mid = (l+r) >> 1;

            }

            tp[l] = a[i];

        }

    }

    if(a[pos] >= tp[p]) {

        p++; // 不必修改最上面的点的高度

    } else {

        a[pos] = 1e9 + 1; // 修改最上面的点的高度,将其变为无穷大

    }

    ans = pos - p;

    p = 1;

    tp[1] = a[pos];

    for(int i = pos + 1; i <= n; i++) {

        if(a[i] <= tp[p]) tp[++p] = a[i];

        else {

            int l = 1, r = p, mid = p >> 1;

            while(l != r) {

                if(a[i] > tp[mid]) r = mid;

                else l = mid + 1;

                mid = (l + r) >> 1;

            }

            tp[l] = a[i];

        }

    }

    ans += (n - pos + 1) - p;

    cout << ans << endl;

    return 0;

}

(4)第四题:闯关

情况:赛中0分,已补题。

题意:小可和达达闯关,有一个通关神器,可以帮助他们俩通关,小可和达达有一个闯关神器,可以让 m 距离变成 k(m<k)这个神器可以在他们中间互相传递,可以在小可手中,也可以在达达手中。但是传递的距离是不能大于q的。请问小可和达达都到达终点(即第 n 个关卡),最少需要使用几次闯关神器。

赛时本题想法:已在比赛过程中详细说明,再次不做过多赘述。

题解:(1)设 f[i][j][1/0] 表示小可在第 个关卡,达达在第 个关卡, 表示目前闯关神器在谁的手上。

每次转移有 种情况,要么小可闯关,要么达达闯关,要么传递神器。

如果这样写动态规划问题的话,我们会发现 f[i][j][0] 可以转移到 f[i][j][1] ,而 f[i][j][1]也可以转移到 f[i][j][0] ,不满足无后效性。但本题是一个求 min 的 ,多次求 min 不会出错,所以可以在原地多转移几次,保证对于某一位置 ,无论第三维是 0 还是 1 ,都转移到了最优解。 每次都可以闯到下一个关卡就行,不必跨越多个关卡。因为本题考虑的是最少传递次数,和关卡通过次数无关。

(2)思路2

可以考虑贪心,如果小可一定要传给达达,则小可最多能到达(达达现在能到达的最远距离+ q)

证明如下:

首先我们保证,神器一定在关卡位置靠后的人手里,可以证明,关卡位置靠前的人在使用完神器之后一定可以归还于另一人。

我们设小可为位置靠后,达达位置靠前,并设小可编号为 A,达达编号为 B, cnt为第 i人的位置,则有 cntA <= cntB。此时神器在 A手中, 在使用完神器之后就将神器跑到最远的能够将神器归还给B

的位置时,一定要将神器归还给 B,因为此时 B也需要使用神器,因为 B也停留在需要使用神器的情况下,否则有 B以到达终点。当 A将神器归还给 B后,随即跑到下一个需要神器的地方。

考虑三个问题:

1、为什么 A使用神器一定能到达到超过 B的地方

答:由于两个关卡之间的间距均小于神器的距离,所以A 在拥有神器之后是一定能到超过 的地方。

2、 A在使用神器结束之后是否一定能够将神器归还给B

答:由于关卡间距中有k<q ,等价于每次将一个以 cntA为中心,长度为 2 * q的线段向右移动 个单位,由于2 * q > k ,所以该线段一定能覆盖到点 cntB。

3、为啥这样次数最少?

答:由于我们每一次交换神器的时机均为对方需要时,且我们不得不给的情况,如果不给会使得对方无法到达到终点,所以该交换次数为最少次数。

直接给出AC代码

代码:

#include <iostream>

#include<cstring>

using namespace std;

const int maxn = 1005;

int a[maxn], b[maxn], dp[maxn][maxn][2];

int main() {

    int n, m, k, q;

    cin >> n >> m >> k >> q;

    for (int i = 1; i <= n; i++) {

        cin >> a[i];

    }

    for (int i = 1; i <= n; i++) {

        cin >> b[i];

    }

    a[n + 1] = a[n];

    b[n + 1] = b[n];

    memset(dp, 0x3f, sizeof dp);

    dp[0][0][0] = 0;

    dp[0][0][1] = 1;

    for (int step = 0; step <= 2 * n; step++) {

        for (int j = 0; j <= min(n, step); j++) {

            int i = step - j;

            if (i > n) continue;

            for (int k = 0, r = 0; r <= 3; k++, r++) { // 枚举第三维时,在原地转移 4次,这样 0 1 都被互相求 min

                k = r % 2;

                if (dp[i][j][k] >= 1e9) continue;

                if (abs(a[i] - b[j]) <= q)

                    dp[i][j][k ^ 1] = min(dp[i][j][k ^ 1], dp[i][j][k] + 1);

                if (k == 0) {

                    dp[i + 1][j][k] = min(dp[i + 1][j][k], dp[i][j][k]);

                    if (b[j + 1] - b[j] <= m) {

                        dp[i][j + 1][k] = min(dp[i][j + 1][k], dp[i][j][k]);

                    }

                }

                else {

                    dp[i][j + 1][k] = min(dp[i][j + 1][k], dp[i][j][k]);

                    if (a[i + 1] - a[i] <= m) {

                        dp[i + 1][j][k] = min(dp[i + 1][j][k], dp[i][j][k]);

                    }

                }

            }

        }

    }

    cout << min(dp[n][n][0], dp[n][n][1]) << endl;

}

总结

  1. 知识点掌握不牢,需要加强模板性的代码的背诵
  2. 考试时要注意把握时间,不要死磕在一道题上
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
CSP-J复赛历年试题pdf是指CSP-J的复赛历年试题的PDF文件。CSP-J是中国计算机学会(CCF)主办的一项高中生计算机科学与编程的竞赛。复赛CSP-J竞赛的第二个阶段,参赛者需要通过初赛选拔才能进入复赛CSP-J复赛历年试题pdf中包含了以往CSP-J复赛的试题和答案。这些试题主要涵盖了计算机科学与编程的各个方面,包括编程语言、算法、数据结构、计算机网络等。参赛者可以通过研究历年试题,了解CSP-J竞赛的题型和出题风格,提高自己的解题能力和编程水平。 CSP-J复赛历年试题pdf对于参赛者来说具有很大的参考价值。通过研究这些试题,参赛者可以了解到高水平的计算机科学与编程问题是如何被设计和解决的。同时,参赛者还可以通过尝试解答这些试题,检验自己的知识和技能,发现自己的不足之处并进行改进。 此外,CSP-J复赛历年试题pdf还对教师和学生进行教学和学习有很大的帮助。教师可以利用这些试题作为教学资源,丰富教学内容和方法,提高学生的计算机科学与编程能力。学生可以通过研究这些试题,扩展知识面,提升解决问题和编程的能力。 总之,CSP-J复赛历年试题pdf是一份重要的学习和竞赛资料,对参赛者、教师和学生都具有很大的参考和帮助作用。通过研究这些试题,大家可以更好地了解计算机科学与编程的相关知识和技能,并提升自己的竞赛和学习能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值