(KDY)CSP-J模拟赛一补题报告
日期:2023年9月30日
文章目录
一、AC情况
第一题 | 第二题 | 第三题 | 第四题 |
---|---|---|---|
WA 90分(赛后AC) | AC 100分 | WA 10分(赛后AC) | WA 10分(赛后AC) |
总分210分。
二、赛中概况
第一题思考后觉得就是一个普通的判断素数,写上模板;第二题看完题,写了一个暴力,大样例也过了;第三题看了之后没有思路,按自己的想法写了一个简单的代码;第四题不会,骗分。
三、解题报告
问题一:数字降级(down)
情况:
WA,赛后AC
题意:
每次操作将数字除以一次它的任意一个因子,问最少几次操作可以将该数字变成一个质数。
赛时做法:
思考后发现只有两种情况:1.这个数本身就是质数,无需操作,输出0;2.这个数不是质数,输出1,因为根据唯一分解定理,一个正整数必然可以写成:
n
=
a
b
(
n
,
a
,
b
∈
N
∗
,
n
≥
2
,
a
是质数
)
n = ab (n,a,b\in N^*,n≥2,a是质数)
n=ab(n,a,b∈N∗,n≥2,a是质数)
于是判断
n
n
n 是否为质数并输出。
题解:
与赛时做法相同,但由于
n
n
n 数据范围过大,
i
∗
i
i * i
i∗i 也会爆
i
n
t
int
int,因此
n
n
n 和判断质数的
f
o
r
for
for 循环里 i * i <= n
的
i
i
i 都是long long 类型。
AC代码:
#include <bits/stdc++.h>
using namespace std;
bool isprime(long long int n) { //判质数
for (long long int i = 2; i * i <= n; i++) { //注意i是long long类型
if (n % i == 0) return false;
}
return true;
}
int main() {
freopen("down.in", "r", stdin);
freopen("down.out", "w", stdout);
long long int n;
scanf("%lld", &n);
if (isprime(n)) {
printf("0");
} else {
printf("1");
}
fclose(stdin);
fclose(stdout);
return 0;
}
问题二:分组(group)
情况:
AC
题意:
将
n
n
n个数分成若干组,每组的得分是这组数没有出现的最小自然数。例如1 2
的得分是0,0 2 3
的得分是1。求几组的最大得分和。
赛时做法&题解:
数据过小,可以统计每个数字出现的个数,每次从0开始遍历,直到当前数字的个数为0,则将0到这个数分为一组,得分为这个数加1,分组后这个组里的每个数的个数减1。有多少个0,就有多少得分大于0的组。
AC代码:
#include <bits/stdc++.h>
using namespace std;
int n, a, cnt[1010];
int main() {
freopen("group.in", "r", stdin);
freopen("group.out", "w", stdout);
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a);
cnt[a]++; //统计数字出现的个数
}
long long int ans = 0;
while (cnt[0] > 0) { //有0即说明有得分大于0的组
int l = 0, num = 0;
while (cnt[l] > 0) { //找连续的数放到这一组里,使得分尽可能地大
cnt[l]--; //这个数分出来到这个组里,冗余个数减1
num = l + 1; //该组得分
l++;
}
ans += num; //统计得分
}
printf("%lld", ans);
fclose(stdin);
fclose(stdout);
return 0;
}
问题三:抢夺地盘(seize)
情况:
WA,赛后AC
题意:
输入一个 n n n 和 q q q ,改变 a 1 a_1 a1 ~ a n a_n an 的几个元素,保证 a 1 a_1 a1 ~ a q a_q aq 为不下降序列, a p a_p ap ~ a n a_n an 为不上升序列。求最小的改变个数。
赛时做法:
q q q左侧,若当前元素小于前一个元素,则将该元素改为当前元素的值,改变个数加1; q q q右侧类同。
题解:
d p dp dp 求解。
求出左侧( 1 1 1 ~ p − 1 p-1 p−1)的最长不下降子序列,用总长度减去序列,即为需要改变的元素个数。
d p [ i ] dp[i] dp[i]:长度为 i i i 的最长不下降子序列的末尾元素
如果新元素可以接在以前的序列后,否则将这一元素放到第一个大于它的元素,覆盖掉。二分查找元素。 d p dp dp 时间复杂度为 Θ ( n l o g n ) Θ(nlogn) Θ(nlogn)。
特判 p p p 元素,如果符合要求,左侧序列长度加1;若不符合要求, p p p 赋值为一个较大值。
同理可得右侧的 d p dp dp 方法。
AC代码:
#include <bits/stdc++.h>
using namespace std;
int n, p, a[100010], ans = 0, dp[100010];
int main() {
freopen("seize.in", "r", stdin);
freopen("seize.out", "w", stdout);
scanf("%d%d", &n, &p);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
//求p左侧的最长不下降子序列
int len = 1;
dp[1] = a[1]; //长度为1初始化为第一个元素
for (int i = 2; i <= p - 1; i++) {
if (a[i] >= dp[len]) dp[++len] = a[i]; //当前元素不小于序列的最后一个元素,直接放到后面
else {
int l = 1, r = len, mid;
while (l < r) {
mid = (l + r) / 2;
if (a[i] < dp[mid]) r = mid;
else l = mid + 1;
}
dp[l] = a[i]; //小于,二分查找序列里第一个大于的元素,替换
}
}
if (dp[len] <= a[p]) len++; //判断p是否符合,符合序列长度增加1
else a[p] = INT_MAX; //不符合,赋最大值,使p不在处理右侧时被重复改变
ans = p - len; //总长度减去最长不下降子序列,即为需要改变的元素
//求p右侧的最长不上升子序列
len = 1;
dp[1] = a[p];
for (int i = p + 1; i <= n; i++) { //修改dp时注意大于等于符号
if (a[i] <= dp[len]) dp[++len] = a[i];
else {
int l = 1, r = len, mid;
while (l < r) {
mid = (l + r) / 2;
if (a[i] > dp[mid]) r = mid;
else l = mid + 1;
}
dp[l] = a[i];
}
}
ans += n - p + 1 - len;
printf("%d", ans);
fclose(stdin);
fclose(stdout);
return 0;
}
问题四:闯关(barrier)
情况:
WA,赛后AC
题意:
两个人走里两条路,每条路上有 n n n 个点,每个点都有到起点的距离 a i a_i ai,两人都最多走 m m m 的距离到下一个点,此时有可以将行走距离变成 k k k 的技能(保证持有技能时, k k k 一定能到达所有点),默认初始时第一个人持有此技能,两个人可以在距离不超过 q q q 的情况下传递技能,两个人全部到达最后一个点视为结束。求传递技能的最小次数。
赛时做法:
无思路,输出0骗分。
题解:
如果 b b b 无法行走,且不持有技能,则让持有技能的 a a a 行走,直到 a a a 走到终点或者 a a a 再走就要超出传递技能的距离 p p p 了(类似贪心的思想,让 a a a 尽可能让技能发挥更大的作用,走更远的距离),此时将技能传给 b b b。
如果 b b b 可以行走,且 b b b 未到达终点,则直接行走。
对 a a a 进行相同的判断处理。
当两人都到达终点是,结束操作。
AC代码:
#include <bits/stdc++.h>
using namespace std;
int n, m, k, q, a[1010], b[1010];
int main() {
freopen("barrier.in", "r", stdin);
freopen("barrier.out", "w", stdout);
scanf("%d%d%d%d", &n, &m, &k, &q);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i++) scanf("%d", &b[i]);
int aa = 0, bb = 0, sq = 1, ans = 0;
while (aa != n || bb != n) { //两人均未到达终点
if (b[bb + 1] - b[bb] > m && sq == 1) { // b无法前进,且不持有技能
while (aa != n && a[aa + 1] - b[bb] <= q) aa++; //a行走
sq = 2; //把技能传递给b(1对应a,2对应b)
ans++; //记录答案
} else if (bb < n)bb++; //
if (a[aa + 1] - a[aa] > m && sq == 2) {
while (bb != n && b[bb + 1] - a[aa] <= q) bb++;
sq = 1;
ans++;
} else if (aa < n)aa++;
}
printf("%d", ans);
fclose(stdin);
fclose(stdout);
return 0;
}
总结
- 第一题 没有考虑完全,注意数据范围,考虑每一个变量的范围;
- 第三题 没有考虑到dp,不掌握dp的转移方程策略,和优化方法,没有想到最长不下降(上升)子序列以及查找的方法;
- 第四题 没有想到贪心的思想和策略。
本次模拟赛主要由于考虑不周全和对算法思想的理解和运用程度不够。