CSP-X 2024 复赛编程题全解(B4104+B4105+B4106+B4107)

前言:发这篇题解是为了弥补我去年的遗憾,去年CSP-X -> 170pts. 所以今年重做一遍后 170pts. -> 400pts.

T1 [CSP-X2024 山东] 刷题:

题目传送门

思路:

这道题考差了一个非常常见的一个算法------贪心,其实这道题的贪心思路是很简单的,首先我们先确定贪心思路,那么我们先把这道题目的条件给拿出来:

  • 件物品的价格为
  • 一张优惠券元,可使用优惠券来免除件物品的价格
  • 即使物品的数量不足m个仍可以使用优惠券

然后题目让我们来求最小值,所以我们要对这些条件进行一个处理,我们肯定知道如果一张优惠券可以免除几个物品的价格的话,那么我们肯定希望让这张优惠券去满足价格大的物品,这样的话,我们自己所花的钱就会尽可能的小,这就是我们这道题的贪心思路,但是有例外情况,如果这几个物品的价格总和如果小于一张优惠券的价格的话,那么肯定单独买更划算,所以我们得对价格进行比较。

那么我们可以先进行一个从大到小的排序,尽可能的将价格大的物品用优惠券抵消掉,当然因为要比较优惠券与单独购买的价格,所以我们得在排序后将数组的前缀和求出来,然后进行比较。

代码:

cpp 复制代码
#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的倍数呢?答案是,证明过程如下:

已知:

假设:

则:

所以:

注:若

那么单独也算一个答案

所以我们在求完前缀和的时候进行对于前缀和的余数进行统计,在按照加乘原理进行计数即可

代码:

cpp 复制代码
#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)

那么,我们需要对修改操作的时间复杂的度进行简化,那么一想到区间检修改就很容易能想到 (线段树)差分,因为修改时间复杂度为,那么这道题的思路就显而易见了,我们可以先创建一个差分数组,来保存区间修改,那么我们来将区间修改进行保存,保存结束后,对差分数组进行前缀和求解,得出来的前缀和数组在进行遍历一遍,如果是奇数,那么证明是正面,反之则是反面

代码:

cpp 复制代码
#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的压轴题来讲,难度还是有的,毕竟思路其实不是很好想(对于小学组正常水平同学),首先,我们先将题目的关键信息提取出来:

  • 一道编程题只能在一天内完成,不可以分多天完成
  • 可以求助小明,省去一道题的做题时间
  • 一天最多可求助小明一次(仅此一次)

我们通过这几条信息可知道这道题的答案具有单调性(最大的耗时最小),所以这道题我们可以二分,二分可以二分最小的做题时间,我们在二分的过程中,还可以借助贪心算法来对答案进行筛选,具体筛选策略如下:

  1. 如果当天可以求助小明的话肯定希望小明解决耗时最大的那道题
  2. 如果当天的时间超出了所需要检查的值的话,那么直接不做
  3. 检查是否能将所有任务全都做完

然后我们根据答案二分即可

小细节:注意二分的上线与下限,不然可能WA

代码:

cpp 复制代码
#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){//昨晚的任务必须小于等于n
            maxx=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;
}
相关推荐
ai安歌4 小时前
【Rust编程:从新手到大师】 Rust 控制流深度详解
开发语言·算法·rust
Shinom1ya_4 小时前
算法 day 36
算法
·白小白4 小时前
力扣(LeetCode) ——15.三数之和(C++)
c++·算法·leetcode
海琴烟Sunshine4 小时前
leetcode 268. 丢失的数字 python
python·算法·leetcode
CL.LIANG4 小时前
视觉SLAM前置知识:相机模型
数码相机·算法
无限进步_4 小时前
深入理解C语言scanf函数:从基础到高级用法完全指南
c语言·开发语言·c++·后端·算法·visual studio
Lei_3359674 小时前
[算法]十大排序
数据结构·算法·排序算法
m0_748240254 小时前
C++仿Muduo库Server服务器模块实现 基于Reactor模式的高性
服务器·c++·php
yuuki2332335 小时前
【数据结构】顺序表+回调函数
c语言·数据结构·后端