CSP-J复赛模拟题2补题报告
2023.10.2 Mon.
S10473吴启瀚
1. 比赛报告
共4题, 第1题70分, 第2题10分, 第3题50分, 第4题0分, 共130分
2. 比赛过程
第一题 人员借调, 思路受限, 没想到可以直接把所有事都做完再回家, 20分钟内出代码(很可能是0分)
第二题 计算, 想到遍历m~n计算, t(i)表i的乘积, s(i)表i的各位之和, ans为当前算出乘积最大的数, (如果有多个积相等,则输出x最小的那个)应该只用判断t(i) > t(ans), 不包括相等, 没用想到达打表, 测试n取最小值1, m取最大值5E+6, 一组测试数据大约要0.8秒, T≤100时间肯定不够用, 25分钟出代码
第三题 智能公交, 想用二分求答案, 但不会写, 最后用遍历, 35分钟出代码
第四题 觉得应该用01背包解, 不知道用几维数组和几层循环, 最后就摆烂了
后来检查时发现第一题有错误, 20min计算+改正
3. 题解
3.1 人员借调 transfer
3.1.1 题目大意:
有n件事要去B地做, 如果在B地待240分钟或以上, 再回A地会被拘留10080分钟, 往返AB一次需要400分钟, 任何事在做的过程中不得中断(包括拘留和往返), 问最少需要几分钟做完所有事(包括拘留和往返)
3.1.2 题目解析:
分两种情况:
- 如果有240min以上的任务, 直接做完所有任务回A地拘留
- 否则判断做完所有任务回A地拘留和往返每次做小于240分钟任务
3.1.3 AC代码:
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
int n, x, ans, sum, cnt;
int main(){
scanf("%d", &n);
for(int i = 1;i <= n;i++){
scanf("%d", &x);
sum += x;
ans += x;
if(sum >= 240){
cnt++;
sum = x;
}
}
if(cnt >= 1){
if(n == 1) ans += 10080;
else ans += min(10080, cnt * 400);
}
printf("%d\n", ans + 400);
return 0;
}
3.2 计算 calc
3.2.1 题目大意:
求数字满足以下要求:
- 在m和n之间
- 各数位上数字之和为k
- 各数位上积最大
- 如果有重复选最小
3.2.2 题目解析:
m, n范围很大, 可以先打表存各数位和sum[], 各数位数字的积pi[]
有sum[x] = sum[x / 10] + (x % 10), pi[x] = pi[x / 10] * (x % 10)
遍历求解即可
3.2.3 AC代码:
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 5e6 + 5;
int T, m, n, k, ans, s[N], t[N];
int main(){
t[0] = 1;
for(int i = 1;i <= N;i++){
s[i] = s[i / 10] + i % 10;
t[i] = t[i / 10] * (i % 10);
}
t[0] = -1;
scanf("%d", &T);
while(T--){
ans = 0;
scanf("%d %d %d", &m, &n, &k);
for(int i = m;i <= n;i++){
if(s[i] == k && t[i] > t[ans]) ans = i;
}
printf("%d %d\n", ans, t[ans]);
}
return 0;
}
3.3 智能公交 transit
3.3.1 题目大意:
有一条连着n个站台, 每两个站台之间的距离都是1km, 有m个乘客, 每个人会从a站到b站台
有一辆车初始在x点, 行驶路线是: x -> a -> b -> x, 问x在哪行驶路线最短
3.3.2 题目解析:
|||__|
1 a b n
分三种情况讨论:
- x在a, b之间
|||__
a x b
移动距离为2*(b-a) - x在a左边
|||__
x a b
移动距离为2*(b-a) + 2*(a-x)
可以看出, 每往左移动1, 就要多走2km - x在b右边
每往右移动1, 就要多走2km
|||__
a b x
移动距离为2*(b-a) + 2*(x-b)
设f[x]表示公交车停在x移动总距离, 用查分完成区间加法, 等差数列加法
3.3.3 AC代码:
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 5e5 + 5;
int n, m, a, b;
long long ans = 4e18, pos, sum, p[N], p1[N], s[N], s1[N];
int main(){
scanf("%d %d", &n, &m);
for(int i = 1;i <= m;i++){
scanf("%d %d", &a, &b);
p[a-1] += 2;
s[b+1] += 2;
sum += 2 * (b - a);
}
for(int i = n;i >= 1;i--){
p[i] += p[i+1];
p1[i] = p1[i+1] + p[i];
}
for(int i = 1;i <= n;i++){
s[i] += s[i-1];
s1[i] = s1[i-1] + s[i];
if(p1[i] + s1[i] < ans){
pos = i;
ans = p1[i] + s1[i];
}
}
printf("%d %d", pos, ans + sum);
return 0;
}
3.4 异或和 exclusive
3.4.1 题目大意:
有n个数字, 已知每个数字的大小a[i], 属于某个集合b[i], 每个集合的收益为所有选取元素之和
最多从中选择m个数字, 使集合收益和最大
3.4.2 题目解析:
用dp[i][j]表示前i个数得到收益j至少需要的数字个数, num[i][j]表示第i组选j个数最大收益值
每一组dp操作后存到num中, 使用分组背包得到最大异或值, dp预处理时i应该循环到组中元素个数
3.4.3 AC代码:
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 2005, M = 2050;
int n, m, f[N][M], num[N][N], f1[M], g[N], v[N][N];
int min(int x, int y){
if(x < y) return x;
return y;
}
int max(int x, int y){
if(x > y) return x;
return y;
}
int main(){
scanf("%d %d", &n, &m);
int t1, t2;
for(int i = 1;i <= n;i++){
scanf("%d %d", &t1, &t2);
v[t2][++g[i]] = t1;
for(int j = 1;j < M;j++)
f[i][j] = 1e9;
}
for(int z = 1;z <= 2000;z++){
if(g[z] != 0)
f[1][v[z][1]] = 1;
for(int i = 2;i <= g[z];i++){
f[i][v[z][i]] = 1;
for(int j = 1;j < M;j++){
if(f[i-1][j] != 1e9){
f[i][j] = min(f[i][j], f[i-1][j]);
f[i][j^v[z][i]] = min(f[i-1][j] + 1, f[i][j^v[z][i]]);
}
}
}
for(int j = 1;j < M;j++)
if(f[g[z]][j] != 1e9)
num[z][f[g[z]][j]] = j;
for(int i = 1;i <= g[z];i++)
for(int j = 1;j < M;j++)
f[i][j] = 1e9;
}
for(int i = 1;i <= 2000;i++){
for(int j = m;j >= 1;j--){
for(int k = 1;k <= g[i];k++){
if(j >= k)
f1[j] = max(f1[j], f1[j-k] + num[i][k]);
}
}
}
printf("%d\n", f1[m]);
return 0;
}
4. 反思
4.1 回顾问题
第1题思考被局限, 第2题没想好怎么优化, 第3题算法不能互相很好的套用, 第4题根本不会做, 没有思路。
4.2 如何改进
感觉不能很好地结合知识, 没法很好的用以前学过的知识, 思路还需打开贯通