日期:2023年10月2日星期一
S08695韩润琦
目录:
1.比赛概况
2.比赛过程
3.题解报告
(1)数字降级
思路
STD
(2)分组
思路
STD
(3)抢夺地盘
思路
STD
(4)闯关
思路1
STD1
思路2
STD2
- 比赛概况
比赛共有四道题,满分400,赛时拿到50分。其中第一题50分,其余全部0分。
- 比赛过程
比赛开始时先把题目大体看了一遍,发现第一题是最简单的,于是就从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分。
- 题解报告
- 第一题:数字降级
情况:赛中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;
}
- 第二题:分组
情况:赛中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;
}
总结
- 知识点掌握不牢,需要加强模板性的代码的背诵
- 考试时要注意把握时间,不要死磕在一道题上