CSP-X 2024 复赛编程题全解(B4104+B4105+B4106+B4107)
前言:发这篇题解是为了弥补我去年的遗憾,去年CSP-X -> 170pts. 所以今年重做一遍后 170pts. -> 400pts.
T1 [CSP-X2024 山东] 刷题:
题目传送门
思路:
这道题考差了一个非常常见的一个算法——贪心,其实这道题的贪心思路是很简单的,首先我们先确定贪心思路,那么我们先把这道题目的条件给拿出来:
- 第
件物品的价格为
- 一张优惠券
元,可使用优惠券来免除
件物品的价格
- 即使物品的数量不足m个仍可以使用优惠券
然后题目让我们来求最小值,所以我们要对这些条件进行一个处理,我们肯定知道如果一张优惠券可以免除几个物品的价格的话,那么我们肯定希望让这张优惠券去满足价格大的物品,这样的话,我们自己所花的钱就会尽可能的小,这就是我们这道题的贪心思路,但是有例外情况,如果这几个物品的价格总和如果小于一张优惠券的价格的话,那么肯定单独买更划算,所以我们得对价格进行比较。
那么我们可以先进行一个从大到小的排序,尽可能的将价格大的物品用优惠券抵消掉,当然因为要比较优惠券与单独购买的价格,所以我们得在排序后将数组的前缀和求出来,然后进行比较。
代码:
#include<bits/stdc++.h>
using namespace std;
int a[2*100005];
long long s[2*100005];
bool vis[2*100005];
bool cmp(int a,int b){return a>b;
}
int main(){int n,m,w;scanf("%d%d%d",&n,&m,&w);for(int i=1;i<=n;i++){scanf("%d",&a[i]);}sort(a+1,a+n+1,cmp);//从大到小排序for(int i=1;i<=n;i++){//求区间和s[i]=s[i-1]+a[i];}long long sum=0;//累加价格for(int i=1;i<=n;i++){if(vis[i]==1){//说明该武平已经被买过了continue;}if(n-i+1<m){//物品不足m个的时候if(s[n]-s[i-1]>=w){sum+=w;for(int j=i;j<=n;j++){vis[j]=1;}}else{sum+=a[i];vis[i]=1;}}else if(s[i+m-1]-s[i-1]>=w){//物品够m个的情况sum+=w;for(int j=i;j<=i+m-1;j++){vis[j]=1;}}else{sum+=a[i];vis[i]=1;}}printf("%lld",sum);return 0;
}
T2 [CSP-X2024 山东] 消灭怪兽:
题目传送门
思路:
这道题是一道数论题目,所涉及到的内容是小学奥数中的同余问题,该题的代码难度较低,但是证明难度较高,我们需要找到所有区间和为k的倍数的区间个数,所以我们肯定是需要一个前缀和来存储区间和,以便以的时间复杂度来查询一个区间的和,那么如何保证一个区间的和是k的倍数呢?答案是
,证明过程如下:
已知:
假设:
则:
所以:
注:若
那么单独也算一个答案
所以我们在求完前缀和的时候进行对于前缀和的余数进行统计,在按照加乘原理进行计数即可
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll a[1000005];
ll s[1000005];
ll r[1000005];
int main(){ll n,k;scanf("%lld%lld",&n,&k);ll cnt=0;for(int i=1;i<=n;i++){scanf("%lld",&a[i]);s[i]=s[i-1]+a[i];//求区间和if(s[i]%k<0) r[(s[i]%k+k)%k]++;//余数统计(若s[i]为负数)else r[s[i]%k]++;//余数统计(若s[i]为正数)}for(int i=0;i<k;i++){//加乘原理进行统计 ll t=r[i];cnt+=t*(t-1)/2;}printf("%lld",cnt+r[0]);//别忘了余数为0的自己也可以组成和为k的倍数的区间return 0;
}
T3 [CSP-X2024 山东] 翻硬币:
题目传送门
思路:
这道题非常巧妙地考到了一个算法——差分,其实在阅读完题目的时候,不难想到差分算法,题目的意思是这样的:
- 有
个正面朝上的硬币
- 每次选择一个区间,将区间内的硬币进行一次翻转
- 求最后的硬币序列(正面为0,反面为1)
那么,我们需要对修改操作的时间复杂的度进行简化,那么一想到区间检修改就很容易能想到 (线段树)差分,因为修改时间复杂度为,那么这道题的思路就显而易见了,我们可以先创建一个差分数组,来保存区间修改,那么我们来将区间修改进行保存,保存结束后,对差分数组进行前缀和求解,得出来的前缀和数组在进行遍历一遍,如果是奇数,那么证明是正面,反之则是反面
代码:
#include<bits/stdc++.h>
using namespace std;
int a[2*100005];
int main(){int n,m;scanf("%d%d",&n,&m);memset(a,0,sizeof a);while(m--){int l,r;scanf("%d%d",&l,&r);//差分,保存区间修改a[l]+=1;a[r+1]-=1;}int s[2*100005];s[0]=0;//前缀和求解for(int i=1;i<=n;i++){s[i]=s[i-1]+a[i];}//判断正反面for(int i=1;i<=n;i++){if(s[i]%2==1){printf("1");}else{printf("0");}}return 0;
}
T4 [CSP-X2024 山东] 刷题:
题目传送门
思路:
本体作为CSP-X 2024的压轴题来讲,难度还是有的,毕竟思路其实不是很好想(对于小学组正常水平同学),首先,我们先将题目的关键信息提取出来:
- 一道编程题只能在一天内完成,不可以分多天完成
- 可以求助小明,省去一道题的做题时间
- 一天最多可求助小明一次(仅此一次)
我们通过这几条信息可知道这道题的答案具有单调性(最大的耗时最小),所以这道题我们可以二分,二分可以二分最小的做题时间,我们在二分的过程中,还可以借助贪心算法来对答案进行筛选,具体筛选策略如下:
- 如果当天可以求助小明的话肯定希望小明解决耗时最大的那道题
- 如果当天的时间超出了所需要检查的值的话,那么直接不做
- 检查是否能将所有任务全都做完
然后我们根据答案二分即可
小细节:注意二分的上线与下限,不然可能WA
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll n,m;
ll a[100005];
bool check(ll t){//二分答案检查int cnt=1;//当天可以做完的任务for(int i=1;i<=m;i++){ll maxx=0;//当天耗时最长的题目ll sum=0;//耗时总和while(cnt<=n){//昨晚的任务必须小于等于nmaxx=max(maxx,a[cnt]);//比较任务耗时大小if(sum+a[cnt]-maxx>t){//时间过多,不做了break;}sum+=a[cnt];cnt++;}if(cnt>n) return 1;//证明能做完}return 0;
}int main(){ll l=0,r=0;scanf("%lld%lld",&n,&m);for(int i=1;i<=n;i++){scanf("%lld",&a[i]);r+=a[i];}while(l<=r){//二分答案ll mid=(l+r)/2;if(check(mid)){r=mid-1;}else{l=mid+1;}}printf("%lld",l);return 0;
}
