当前位置: 首页 > news >正文

CSP-J复赛模拟赛2 王晨旭补题 2025.10.2

一,前言

生日过完了呜呜,昨天刚说完就退步,打的不是很好,这套的质量非常高(尤其第三题),感觉难度回到了去年的水准,真是说着感觉我这一年毫无提升似的(算法能力的提升是有的,这两套的第二题比去年做的水平都提了不少(本次第二题对比2024.10.1翻翻转转),但是感觉思维能力又跟不上,每题的算法都用的很死板,题目遇到问题生搬硬套,不会变通,害怕失误的同时又犯了很多不应该犯的错,好像有点瓶颈了,这套的第一题就是很好的例子),打完这套明显感觉现在是有一定问题的,但还没有摸到本质,在调整一下看明天成绩吧(有点焦虑现在,最后一年csp-j了,压力很大)

本篇篇幅很长,前三个题基本都是精华

二,成绩

1,人员借调【20/100】我在干什么,这么水的一道题,做的时候是被顶号了吗

2,计算【0/100】申诉后【80/100】骗你的,其实骗分都能骗70(多组数据不换行,该)

3,智能公交【50/100】不敢睁开眼竟然本题是最高分,好骗兄弟

4,异或和【0/100】爽捐一手

总分:【150/400】今天班级Rank很有意思,申诉后我依然是Rank4,但是前5的人员变化非常大

今天的成绩期望能到250的,然而不尽如人意,本次班1:220分

三,题一

p1:狠狠的骂自己,就纯懒,懒死我了,纯模拟,怕动了一点脑子就会出错,于是纯按题目流程模拟干了50行,一点都不合并,400都拆成两个200来加(我在干嘛),说是求稳,能对也行,但这个题不是纯模拟,涉及一定贪心思想(后文说),傻傻的根本没考虑,给我一种完全是机械化不带一点人类思想的感觉,我写这个题的时候像是非常弱的那种原始AI顶号写出来的,其实就是蠢和懒呗,胆怯怕写错,于是对着题面完全转换着写下来(要这么简单还考它干嘛),隐藏信息也不分析,忘记了做题的初衷是凭借自己的思考来解决问题,陷入思维定式,样例过了也就举不出反例,硬说服自己写的是对的,于是落了个20分的结局

p1延展:上篇也提过自己的自信不够,可能也有这一部分原因导致我需要计算的部分全部按照最原始的方式写的,而自信不够的原因其实也就是思维题刷的不够,不熟练

p2:本题在班中两极分化,要不100,要不20,就是隐藏情况是否有没有考虑到,先放题面

小可在 A 地工作能力非常出众,B 地的领导想借调小可帮忙处理 件事情,小可处理第 i 件的耗时为a_{i}分钟。正常借调的过程为小可从 A 地到 B 地进行处理,处理结束之后回到 A 地。
但是由于小可能力过于突出,如果小可在 B 地待连续大于等于 240 分钟时,A 地领导会非常的不高兴,将强制把小可留在 A 地 7天(10080分钟),7天结束后小可可以继续留在 A 地正常工作或者继续前往 B 地帮忙处理事情。
于是,小可有了一个对策,在 240 分钟快到的时候就此 B 地回到 A 地,然后再去 B 地,这样的话 240 分钟就会重新计时。注意:从 A 地往返一次 B 地会耗时400分钟(这个时间不计算到待在 B 地的时间)。
现在小可从 A 地准备出发,需要在 B 地处理完所有事,然后回到 A 地正常的工作。
注意:n 件事情不可以打乱,并且开始处理一个事情时,这个事情必须处理完才可以继续后面的内容(无论是回 A 地还是继续处理后面的事情)。
请问小可至少需要多少分钟?

根据题面直接写出的赛场代码,为了模拟人物行动还将往返拆开了(?非人行为),太害怕出错还补上了注释(蠢哭了)

#include<iostream>
#define ll long long
using namespace std;
int main(){freopen("transfer.in","r",stdin);freopen("transfer.out","w",stdout); ll n,b[1005];cin>>n;for(int i=1;i<=n;i++){cin>>b[i];}ll ti=0,ans=0,wz=0;//wz为0代表在A地,为1在B地 for(int i=1;i<=n;i++){if(!wz){//要开始在B地处理 ans+=200;//去B地做任务 wz=1;}if(ti+b[i]>=240&&ti==0){//无法使用对策if(wz){//在B地 ans+=200;//回去 wz=0;} ans+=b[i]+10080;//做完任务受罚 (赛后:你这么老实干什么)} else if(ti+b[i]>=240&&ti!=0){//对策 if(wz){ans+=200;//返 wz=0;}ti=b[i];//刷新时间重新计时 wz=1;//回到B地 ans+=b[i]+200;//往 }else if(ti+b[i]<240){//继续留在B地 ti+=b[i];ans+=b[i];}}if(wz){//没回到A地 ans+=200;wz=0;}cout<<ans;
} 

可以看到基本还原题目,但是看这组样例

2

240 240

将会受两次罚+2倍10080,那我为什么不直接干完再回来只受一次罚呢(贪心),这一点我的纯按步机械模拟几乎是无法实现的(这一步是全局操作(判断有没有受罚可能)),所以打击非常大

p3:正解思路

首先,本题先判断是否一定会受罚(方案1),再判断是否要选择受罚(方案2,方案3取最小)

方案1:必然会受罚(任务中有>240的,不管怎么往返,均要受罚,那就省去往返次数):任务总和时间+开始和结束的往返一次B地400+受罚10080

方案2:不一定会受罚(利用策略,来回往返节省时间):任务总和时间+遍历计算出需要往返的时间n*400+开始和结束的往返一次B地400

方案3:不一定会受罚(为避免受罚,利用策略往返的次数过多,时间甚至大于直接受罚(需经过比较)):与方案1时间相同

根据这3种方案,AC代码就出现了

#include<iostream>
using namespace std;
int a[1005],ans1,ans2,sum,n;
bool flag;
int main(){cin>>n; for(int i=1;i<=n;i++){scanf("%d",&a[i]);ans1+=a[i];if(a[i]>=240){flag=1;}} if(flag){//方案1printf("%d\n",ans1+400+10080);return 0;}ans2=ans1;//不能直接即用,不然没法比最小for(int i=1;i<=n;i++){if(sum+a[i]>=240){sum=a[i];ans2+=400;}else{sum+=a[i];}} printf("%d\n",min(ans1+400+10080,ans2+400));//方案3,方案2取最小
} 

四,题二

p0:换行一直在针对我,多组数据一定记得换行啊啊啊啊!!!!!不换行没有分的

p1:本题对我的提升无疑是巨大的,三年的两个处女秀都在这题

第一次使用二分查找

第一次使用重载运算符

p2:本题先放简化后题面,再放考场过程和思路

给你一个区间 l r,再给你一个k

求在l到r区间内满足数位之和等于k,在满足数位之和的数中,输出数位之积最大的数,若数位之积有相同的,则输出最小的数

p3:我写了很长一段话,最后都删了,我以为我的思路是错误的,但是,看过老师代码后,思路完全一样,甚至我的在二分部分上更加简洁(重载运算符应用),经过我的小修改,结合老师的递归优化求数位之和,之积,直接A掉,也是没有辜负期望

先说一下改掉了什么,只有几行

源代码:将符合条件的数及其数位之积构成pair后放入vector,再将vector按照排序规则sort,输出vector的第一个元素

更改后:返璞归真,直接循环遍历求最大比较

打这一场的时候我疯了吗,连比最大值都不会了,去年的树组也是,一眼就看出怎么把O(nm)压掉,这次是一眼把O(nlogn)压掉,以为多这一点无所谓,但其实真的致命

p4:直接上我的思路

首先,离线处理,自己推导递归公式,在O(n)范围内直接将所有5e6范围内数字的数位之和,之积存入数组,递归:一是能够利用之前求解的值,二是压掉数位分离的while循环

第二步,sort排序,确保序列能够进行二分,因为要查找满足数位之和为k的数的范围,所以以数位之和从小到大排序

第三步,二分查找数位之和>=k的第一个数,确定满足题目条件的最小值,再二分查找数位之和>=k+1的第一个数,-1后正好是满足题目条件的最大值

第四步,遍历范围,if判断求出该范围中,最小的数位之积最大的数

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int T,mul[5000010];
struct node{//第一步筛选符合和为10的数,压时间复杂度采用二分 int id,p;node(){}node(int id,int p):id(id),p(p){}bool operator<(const node &t)const{return p<t.p;}
}t[5000010];
bool cmp(node a,node b){return a.p<b.p;
}
int main(){//freopen("calc.in","r",stdin);//freopen("calc.out","w",stdout); mul[0]=1;for(int i=1;i<=5000005;i++){t[i].id=i;t[i].p=t[i/10].p+i%10;mul[i]=mul[i/10]*(i%10);}sort(t+1,t+5000006,cmp);cin>>T;while(T--){int l,r,k;scanf("%d%d%d",&l,&r,&k);int l1=lower_bound(t+1,t+5000006,node(0,k))-t;int r1=lower_bound(t+1,t+5000006,node(0,k+1))-t-1;int maxx=-1,maxi=0x3f3f3f3f; for(int i=l1;i<=r1;i++){if(t[i].id>=l&&t[i].id<=r){//在区间范围内直接入 if(mul[t[i].id]>maxx){maxx=mul[t[i].id];maxi=t[i].id;}else if(mul[t[i].id]==maxx){if(t[i].id<maxi){maxi=t[i].id;}}}}printf("%d %d\n",maxi,maxx);}
} 

p5:补充

递归:

比自己数位短一位的肯定已经先求解出来了,那这一位的数位之和就等于比自己短一位的数位之和加上自己的末位

t[i].p=t[i/10].p+i%10;

乘积同理(记得初始化)
mul[i]=mul[i/10]*(i%10);

重载运算符:

结构体二分查找的关键是将要查找的值包装成结构体:

node(){}
node(int id,int p):id(id),p(p){}
//这两行至关重要,少一行都不行

二分查找内部操作涉及比较,所以要重载<,与cmp函数的重载完全相同

bool operator<(const node &t)const{return p<t.p;
}

五,题三

p1:场上没什么好说的,只要理解样例,50到手(怎么给这玩意干成最高分了)

p2:题面走起

马路上总共有n个公交站台,编号从1,2,...,n,有一辆智能公交车会在这n个站台之间穿梭。如果智能公交上没有乘客,那么智能公交就会停靠在x站台。
有人要乘坐智能公交,只需要按动公交站台上的按钮,智能公交就会快到到达相应的站台。
例如,有人想从第五个公交站台到第十个公交站台,那么公交车会先从先从x个站台跑到第五个站台,然后再从第五个站台跑到第十个站台,然后再回到第x站台停下。(智能公交可以随意的双向移动,不需要考虑智能公交是否掉头转弯等因素)假设相邻的两个站台的距离都是 千米,那么智能公交总共行走了\left | x-5\right |+\left | 5-10 \right |+\left | 10-x \right |的距离。
现在有m个人要依次乘坐智能公交,每个人都会等待智能公交停在x站台之后在按动当前站台按钮准备乘坐公交。现在已知第i个人都是从a站台到b站台。请你计算x,使得智能公交移动距离最短。最终输出x和最短的距离,x若有多个,输出最小的一个。

p3:本题真是年度好题,两种做法,我们直接上思路和AC代码

思路一:数学方法及前缀和差分应用

x<a时:若停靠位置在a-1,则公交车要多移动2距离。若停靠位置在a-2,则公交车要多移动4距离。我们可以发现,停靠位置从a-1到1,公交车多移动的距离呈公差为2的等差数列。

x>b时:同理,停靠位置从b+1到n,公交车多移动的距离同样呈公差为2的等差数列

那么本题公交停靠在x的总行动距离如下(站台之间,站台a到x,站台b到x)

每次都是往返所以肯定是2倍:

2 \sum_{i=1}^{n}(b_{i}-a_{i})+2\sum_{b_{i}<x}(x-b_{i})+2\sum_{a_{i}>x}(a_{i}-x)

我们假设f[x]表示公交车停靠在x的总移动距离,那么给定a,b的时候,相当于将整个数组全部加2*\left | a-b \right |(往返),简略计算,将1到a-1的位置额外加一个公差为2的等差数列;同理,将b+1到n的位置加一个公差为2的等差数列。

于是,上方公式化简可得

2 \sum_{i=1}^{n}(b_{i}-a_{i})+2\sum_{b_{i}<x}x-2\sum_{b_{i}<x}b_{i}+2\sum_{a_{i}>x}x-2\sum_{a_{i}>x}a_{i}

#include <bits/stdc++.h>
using namespace std;
int main() {int n, m, a, b;long long ans = 1e18, pos, sum = 0;cin >> n >> m;vector<long long> pre(n + 2), suf(n + 2), pre1(n + 2), suf1(n + 2);while (m--) {cin >> a >> b;pre[a - 1] += 2;suf[b + 1] += 2;sum += (b - a) * 2;//公式体现 }for (int i = n; i >= 1; i--) {pre[i] += pre[i + 1];pre1[i] = pre1[i + 1] + pre[i];}for (int i = 1; i <= n; i++) {//枚举x suf[i] += suf[i - 1];suf1[i] += suf1[i - 1] + suf[i];if (suf1[i] + pre1[i] < ans) {pos = i;ans = suf1[i] + pre1[i];}}cout << pos << " " << ans + sum << endl;
}

思路二:贪心

发现上文公式中b_{i}-a_{i}永远不变,关乎答案的是另外两项x到各个站牌之间的距离,要使其最小,而这时,站牌的起点和终点站不再有区别,就如同一个个货仓排列着,当即!(燃起来了)转化成为贪心例题货仓选址,取各个站点的中位数

在一条数轴上有N家商店,它们的坐标分别为A1~AN
现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。

为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。

解析:我们把 A[0]~A[N – 1]排序,假设货仓在 X 坐标处,X 左侧的商店有 P 家,右侧的商店有Q 家。如果 P<Q,则每把货仓的选址向右移动 1 单位距离,距离之和就会变少 Q – P。同
理,如果 P > Q ,则货仓的选址向左移动会使距离之和变小。当 P == Q 时为最优解

最优解即为中间值,本题20行就可以完成了(正解代码考虑篇幅就不放了,非常简单)

番外:本题也可以三分完成(二分都没学明白我就别误闯天家了)

六,题四

p1:这集依然没有场上,直接上题

多个集合中总共有n个数字,并且已知每个数字的大小a_{i}和属于某个集合b_{i}
在一个集合中选择一个数字,收益为这个数字的大小,选择多个数字,收益为这些数字的异或和。总收益为每个集合的收益之和。注意:最多从中选择m(m<n)个数字,使这些数字总收益最大。

p2:思路

建立数组f[i][j],表示看到第i个数,能否异或出j (能为0,不能为1)--拿下10分

然后考虑加入m的限制。 在前一种情况的基础上,f 数组里存的值仅是0或1,没有得到充分的利用,因此可以将 f 存储的内容变为看到第i个数,异或出j至少所需要的数字个数。
但此时转移时需要求 min ,无法异或的情况不能用0来表示,可以用 1e9 来表示--拿下20分

此时不难想到对于每一个集合进行一次如上操作,但若这样开成三维数组会超内存,故可以之用二维数组,但每次使用后清空。这样就需要再开一个数组 表示第i组选j个数最大的异或值。每一组结束 dp 预处理操作后存入 num 中,最后使用一个分组背包即可得到最大异或值。但是要注意,在每一组 dp 预处理时, 应循环到组中元素的个数而不是n否则会超时。 由于n的总值仍是2000 ,所以操作时组和i的循环次数总和仍为2000,时间复杂度不变。--AC

p3:代码(毫无废话)

#include <bits/stdc++.h>
using namespace std;
int n, m, dp[2005][2050], num[2005][2005] = {}, dpp[2050] = {}, zz[2005] = {};// dp[i][j]表示看到第i个数,异或出j至少所需要的数字个数,num[i][j]表示第i组j个数最大的异或值
vector<int> ve[2005];
int main() {cin >> n >> m;for (int i = 1; i <= n; i++) {int x, y;cin >> x >> y;ve[y].push_back(x);zz[y]++;}for (int i = 1; i <= n; i++) {for (int j = 1; j <= 2047; j++) {dp[i][j] = 1e9;}}for (int zu = 1; zu <= 2000; zu++) {if (zz[zu] != 0) dp[1][ve[zu][0]] = 1; // 不加判断的话会访问越界导致运行崩溃for (int i = 2; i <= zz[zu]; i++) {for (int i = 2; i <= zz[zu]; i++) {dp[i][ve[zu][i - 1]] = 1; // 这一步很重要,不要忘记for (int j = 1; j <= 2047; j++) {for (int j = 1; j <= 2047; j++) {if (dp[i - 1][j] != 1e9) {dp[i][j] = min(dp[i][j], dp[i - 1][j]);dp[i][j ^ ve[zu][i - 1]] =min(dp[i - 1][j] + 1, dp[i][j ^ ve[zu][i - 1]]);}}}for (int j = 1; j <= 2047; j++) {if (dp[zz[zu]][j] != 1e9) num[zu][dp[zz[zu]][j]] = max(num[zu][dp[zz[zu]][j]], j);}for (int i = 1; i <= zz[zu]; i++) {for (int j = 1; j <= 2047; j++) {dp[i][j] = 1e9;}}}for (int i = 1; i <= 2000; i++) {for (int j = m; j >= 1; j--) {for (int k = 1; k <= zz[i];k++) { // 最后的枚举,只枚举到本组的个数if (j >= k)dpp[j] = max(dpp[j], dpp[j - k] + num[i][k]);}}}cout << dpp[m];
}

七,结语

这一篇的篇幅真的很长(8000字),第二天下午才肝完,回看倒是考的也还可以,比去年肯定有所进步,不应该给自己太大压力了,反而会影响自己,不是吗?(抓紧收尾我不行了)

八,祝福

祝:轻松愉快每一天

http://www.dtcms.com/a/438391.html

相关文章:

  • 理解Consumer<T>
  • Linux:了解Socket编程
  • UE4报错:无法编译项目
  • Cobalt Strike
  • 牛客算法基础noob61 字符串操作
  • 快速制作简单的网站网站建设 中山
  • 网络爬虫技术规范与应用指南系列(xc—1)
  • CSS Border(边框)
  • Photoshop - Photoshop 工具库
  • 2.1.2 扩展知识:AI 语音通话
  • 动态目标检测与跟踪:基于卡尔曼滤波的门限关联与可视化全流程
  • Codeforces Round 863 A. Insert Digit (1811)
  • 企业网站建立要做的准备更新电脑版wordpress
  • 阳江做网站公司南昌seo优化公司
  • 光子桥揭开可调激光PIC的面纱
  • C++——类和对象3
  • Linux第二十四讲:多路转接epoll
  • 专业网站定制流程网站建设公司是怎么找客户
  • Unity学习之C#的反射机制
  • Python环境管理工具全景对比:Virtualenv, Pipenv, Poetry 与 Conda
  • 郑州企业如何建网站wordpress微信付费
  • 微信小程序入门学习教程,从入门到精通,微信小程序开发进阶(7)
  • 数据结构和算法篇--带哨兵节点的双链表
  • 6黄页网站建设做网站怎么去工信部缴费
  • 三支一扶面试资料
  • pytorch 52 基于SVD从全量训练模型中提取lora模型
  • Process Monitor 学习笔记(5.7):长时间运行追踪与日志体积控制
  • 深入解析需求变更:从本质认知到实践指南
  • 商城网站建设的步骤网络设计教程
  • Day 30 - 错误、异常与 JSON 数据 - Python学习笔记