C++ 2019-2022 CSP_J 复赛试题横向维度分析(上)

本文对 2019、2020、2021、2022 4年来 CSP_J 复赛的笔试题目以横向维度进行比较,希望对参加复赛的学生有帮助。本文在讲解每一道题目时,仅提供题目的基本要求,更多细节,请自行查阅其它有关文档。

1. 第一题

1.1 2022

题目

乘方pow

题目描述

小文同学刚刚接触了信息学竞赛,有一天她遇到了这样一个题:给定正整数ab,求ab 的值是多少。abba相乘的值,例如23即为32相乘,结果为2x2x2=8

“简单!”小文心想,同时很快就写出了一份程序,可是测试时却出现了错误。

小文很快意识到,她的程序里的变量都是int 类型的。在大多数机器上,int类型能表示的最大数为231-1,因此只要计算结果超过这个数,她的程序就会出现错误。由于小文刚刚学会编程,她担心使用int计算会出现问题。因此她希望你在ab的值超过109时,输出一个 -1进行警示,否则就输出正确的ab的值。

然而小文还是不知道怎么实现这份程序,因此她想请你帮忙。

分析问题:

第一道题目属于送分题,一般都不难,要说简单也不简单,注意细节把控,认真审查题目的每一个要求。

本题确实简单,就是一个求幂的运算,本质就是一个累乘算法。但是如果直接使用 pow函数,肯定是不行的。在给定 a、b的情况下,使用 pow函数要么得到正确结果,要么就直接溢出。很明显,此题是要求找到溢出的临界点。

在解决问题时,需要站在数学角度,把一些特殊情况考虑进去。如 a=1、b=1时。

编码实现:

#include <iostream>
using namespace std;
#define ll long long int
ll a, b, c = 1000000000;
ll cal(ll a, ll b) {
	ll ret = 1;
    //累乘
	for (int i= 1; i <= b; i ++) {
		ret *= a;
	}
	return ret;
}
int main() {
	cin >> a >> b;
	if (a == 1) {
        //考虑 a=1 的情况
		cout << 1;
		return 0;
	}
    //累乘器
	ll ji = 1;
    //统计累乘次数
	int cs = 0;
	while (1) {
		if (ji * a > c) break;
		ji*= a;
		cs ++;
	}
	if (b > cs) {
		cout << -1;
	} else {
		cout << cal(a, b);
	}
	return 0;
}

1.2 2021

题目:

分糖果

题目描述:

红太阳幼儿园有 n 个小朋友,你是其中之一。保证 n>=2。有一天你在幼儿园的后花园里发现无穷多颗糖果,你打算拿一些糖果回去分给幼儿 园的小朋友们。 由于你只是个平平无奇的幼儿园小朋友,所以你的体力有限,至多只能拿 R块糖回去。
但是拿的太少不够分的,所以你至少要拿 L 块糖回去保证 n<=L<=R。也就是说,如果你拿了 k块糖,那么你需要保证 L<=k<=R
如果你拿了 k 块糖,你将把这 k 块糖放到篮子里,并要求大家按照如下方案分糖果:

只要篮子里有不少于n块糖果,幼儿园的所有 n个小朋友(包括你自己)都从篮子中拿走恰好一块糖直到篮子里的糖数量少于n 块。此时篮子里剩余的糖果均归你所有,这些糖果是作为你搬糖果的奖励.。

作为幼儿园高质量小朋友,你希望让作为你搬糖果的奖励的糖果数量(而不是你最后获得的总糖果数量!)尽可能多;因此你需要写一个程序,依次输入n,L,R,并输出你最多能获得多少作为你搬糖果的奖励的糖果数量。

分析题目

此题本质是求 n 余数问题。因为 n的最大余数是 n-1,也就是要在LR 之间查找一个 k看是否满足其除以 n的余数接近或等于n-1

编码实现:

思路一:

可以先计算一个x 值,让其满足L<=x*n<=R。如果有满足其要求的值,则最大值一定是 n-1。如果不存在,则最大值为 r。针对不同的LRK可能会有多个,注意本题不是求 K,而是求 kn的最大余数。

#include <iostream>
using namespace std;
int main() {
	int n,l,r;
	cin>>n>>l>>r;
	int i,j;
	i= l%n==0?l/n:l/n+1;

	if( n*i>=l && n*i<=r  )
		cout<<n-1;
	else
		cout<<r%n;
	return 0;
}

思路二:

#include <iostream>
using namespace std;
int n, l, r;
int main() {
    cin >> n >> l >> r;
    // 最少可以发L/n轮,每轮发n颗糖
    int x = l/n *n;
    L -= x; 
    r -= X;
    if (r < n-1) {// 如果r<n-1,余数最大只能到r
        cout << r;} 
    else { // 如果r>=n-1,一定可以选n-1那个作为余
        cout << n-1;
return 0;

1.3 20 20

题目:

优秀的拆分

题目描述:

一般来说,一个正整数可以拆分成若干个正整数的和。例如, 1 = 1, 10 = 1 + 2 + 3 + 4 等。
对于正整数 n 的一种特定拆分,我们称它为“优秀的”,当且仅当在这种拆分下, n 被分解为了若干个不同的 2 的正整数次幂。注意, 一个数 x 能被表示成示成 2 的正整数次幂,当且仅当 x 能通过正整数个 2 相乘在一起得到。
例如, 10 = 8 + 2 = 23 + 21 是一个优秀的拆分。但是, 7 = 4 + 2 + 1 = 22 + 21 + 20 就不是一个优秀的拆分,因为 1 不是 2 的正整数次幂。
现在,给定正整数 n,你需要判断这个数的所有拆分中,是否存在优秀的拆分。 若存在, 请你给出具体的拆分方案。

十进制可以转换为二进制,可以得到任何一个正整数都能转化为 n=1+2+4+8+16+32……形式。

编码实现:

#include <iostream>
using namespace std;
int main() {
	int a[100];
	int idx=0;
	int n,base=1;
	cin>>n;
	if( n & 1==1 ) {
		//是奇数
		cout<<-1;
		return 0;
	}
	while( n>0) {
		if( n & 1==1 ) {
			a[idx++]=base;
		}
		base*=2;
		n=n>>1;
	}
	for(int i=idx-1; i>=0; i--) {
		cout<<a[i]<<"\t";
	}
	return 0;
}

1.4 2019

题目:

数字游戏

问题描述:

K同学向小P同学发送了一个长度为801字符串来玩数字游戏,小P同学想要知道字符串中究竟有多少个1
注意:01字符串为每一个字符是0或者1的字符串,如“101”不含双引号)为一个长度为301 字符串。

分析问题:

本题目很简单,因为长度固定在8,直接循环字符串即可得到最终答案。还有一种更简单的方案,使用位运算符。

编程实现:

总感觉下面的代码就点傻,缺少点灵性。一道竞赛题目不至于这么简单,而实际上就是这么简单。

#include <bits/stdc++.h>
using namespace std;
string str;
int cnt[2] = {0};
int main() {
	cin >> str;
	for(int i = 0; str[i]; i++)
		cnt[ str[i]-'0' ]++;
	cout << cnt[1] << endl;
	return 0;
}

2. 第二题

2.1 2022

题目:

解密 decode

题目描述:

给定一个正整数k,有k 次询问,每次给定三个正整数ni,ei,di,求两个正整数 pi,gi,使ni=pi x gi、 ei x di=(pi - 1)(gi - 1) + 1

输入格式

第一行一个正整数 k,表示有 k 次询问。

接下来k行,第i行三个正整数 ni,di,ei

输出格式

输出k行,每行两个正整数 pi,qi表示答案。

为使输出统一,你应当保证 pi<= qi。如果无解,请输出 NO

输入输出样例

输入#1 输出#1

10 2 385

770 77 5 NO

633 1 211 NO

545 1 499 NO

683 3 227 11 78

858 3 257 3 241

723 37 13 2 286

572 26 11 NO

867 17 17 NO

829 3 263 6 88

528 4 109

数据范围

以下记m=n-exd+2

保证对于 100%的数据,1<=k<=105,对于任意的 1<=i<=k,1<=ni<=10181<=ei X di<=1018,1<=m<=109

分析问题:

根据已知条件推导后,可知就是根据已知的系数,求解一元二次方程式的解。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

#include <iostream>
#include <cmath>
using namespace std;
#define ll long long
ll k, n, d, e, m, p, q;
int main() {
	cin >> k;
	for (ll i= 1; i <= k; i++) {
		cin >> n >> d >> e;
		m=n-e*d+2;// p+q = m,pxq=n
		ll x=m*m-4*n;
		ll y = sqrt(x);
		if(x == y*y) {
		    // 有整数解
			p=(y+m) / 2;
			q=m-p;
			cout << min(p,q)<<" "<<max(p,q) << endl;
		} else {
		// 没有整数解
			cout <<"NO" << endl;
		}
	}
	return 0;
}

2.2 2021

题目:

插入排序

问题描述:

插入排序是一种非常常见且简单的排序算法。小 Z是一名大一插入排序的新生,今天 ·H·老师刚刚在上课的时候讲了插入排序算法。
假设比较两个元素的时间为 O(1),则插入排序可以以O(n^2) 的时间复杂度完成长度为 n 的数组的排序。不妨假设这 n个数字分别存储在 a1,a2,··,an之中,则如下伪代码给出了插入排序算法的一种最简单的实现方式:下面是c/C++的示范代码

for (int i = 1; i <= n; i++)
    for (int j = i; j>=2; j--)
        if ( a[j] < a[j-1] ){
            int t = a[j-1];
            a[j-1] = a[j];
            a[j] = t;
        }

为了帮助小Z更好的理解插入排序,小Z的老师 H 老师留下了这么一道家庭作业:

H 老师给了一个长度为 n的数组 a,数组下标从 1开始,并且数组中的所有元素均为非负整数。小 Z需要支持在数组 a上的 Q次操作,操作共两种,参数分别如下:

1xv:这是第一种操作,会将 a的第 x个元素,也就是 ax 的值,修改为 v。保证 1<=x<=n,1<=v <=109。这种操作会改变数组的元素,修改得到的数组会被保留,也会影响后续的操作。

2x:这是第二种操作,假设 H老师按照上面的伪代码对 a数组进行排序,你需要告诉 H 老师原来a 的第 x个元素,也就是 ax,在排序后的新数组所处的位置。保证 1<=x<=n。注意这种操作不会改变数组的元素,排序后的数组不会被保留也不会影响后续的操作。

分析问题:

结构化原数组中的数据,每一个数据当前数组中的位置为唯一编号,排序后,使用此唯一编号存储排序后的位置。

编码实现:

#include <bits/stdc++.h>
using namespace std;

int n,q;
int pos[100];

struct ele {
	//值
	int val;
	//原来位置
	int pos;
};
ele a[100];
/*
*  比较算法
*/
bool cmp(ele e1,ele e2) {
	//由小到大排序
	return e1.val==e2.val?e1.pos<e2.pos:e1.val<e2.val;
}

int main() {
	cin>>n>>q;
	for(int i=1; i<=n; i++) {
		cin>>a[i].val;
		a[i].pos=i;
	}
	sort(a+1,a+1+n,cmp);
	for(int i=1; i<=n; i++) {
		pos[a[i].pos]=i;
	}

	for(int i=1; i<=q; i++) {
		int opt,x,val,idx,val_;
		cin>>opt>>x;
		if(opt==1) {
			cin>>val;
			for(int j=1; j<=n; j++) {
				if(a[j].pos==x) {
					val_=a[j].val;
					a[j].val=val;
					idx=j;
					break;
				}
			}
			if(val>val_) {
				for(int j=idx+1; j<=n; j++) {
					if( a[j].val<a[j-1].val  ||  a[j].val==a[j-1].val && a[j].pos<a[j-1].pos ) {
						ele tmp=a[j];
						a[j]=a[j-1];
						a[j-1]=tmp;
					} else {
						break;
					}
				}
			} else if(val<val_) {
				for(int j=idx-1; j>=1; j--) {
					if( a[j].val>a[j+1].val  ||  a[j].val==a[j+1].val && a[j].pos>a[j+1].pos ) {
						ele tmp=a[j];
						a[j]=a[j+1];
						a[j+1]=tmp;
					} else {
						break;
					}
				}
			}
			for(int j=1; j<=n; j++) {
				pos[a[j].pos ]=j;
			}
		} else if(opt==2) {
			cout<<pos[x]<<endl;
		}

	}
	return 0;
}

2.3 2020

题目:

直播获奖

题目描述:

NOI2130即将举行。为了增加观赏性,CCF决定逐一评出每个选手的成绩,并直播即时的获奖分数线。本次竞赛的获奖率为w%,即当前排名前 w%的选手的最低成绩就是即时的分数线。
更具体地,若当前已评出了p个选手的成绩,则当前计划获奖人数为max(1,[p x w%]),其中w是获奖百分比,[x] 表示对x向下取整,max(x,y)表示xy中较大的数。如有选手成绩相同,则所有成绩并列的选手都能获奖,因此实际获奖人数可能比计划中多。
作为评测组的技术人员,请你帮CCF写一个直播程序。

输入格式:
1行两个正整数n,w。分别代表选手总数与获奖率。

2行有n个非负整数依次代表逐一评出的选手成绩。

输出格式
只有一行,包含n个非负整数,依次代表选手成绩逐一评出后,即时的获奖分数线。相邻两个整数间用一个空格分隔。

样例 1输入

10 60
200 300 400 500 600 600 0 300 200 100

样例1输出
200 300 400 400 400 500 400 300 300

题目分析

本质就是一个排序问题,可以使用计数排序。计算出人数后,再由高向低依次输出。

编码实现:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, w, b[605];
int main(){
	cin >> n >> w;
	for(int i = 1; i <= n; i++) {
        int v;cin >> v;
        b[v]++;
        int cnt = max(1, i* w / 100);
        int k = 600, sum = 0;
        while (k >= 0) {
            if (sum + b[k] < cnt) {
                sum += b[k];
                k --;
            }
            else {
                cout << k <<"	";
                break;
            }
        }
    }
	return 0;       
}

2.4 2019

题目:

公车换乘

问题描述:

著名旅游城市 B 市为了鼓励大家采用公共交通方式出行,推出了一种地铁换乘公交车的优惠方案:

  1. 在搭乘一次地铁后可以获得一张优惠票,有效期为 45 分钟,在有效期内可以消耗这张优惠票,免费搭乘一次票价不超过地铁票价的公交车。在有效期内指开始乘公交车的时间与开始乘地铁的时间之差小于等于 45 分钟,即;
    tbus - tsubway ≤ 45

  2. 搭乘地铁获得的优惠票可以累积,即可以连续搭乘若干次地铁后再连续使用优惠票搭乘公交车。

  3. 搭乘公交车时,如果可以使用优惠票一定会使用优惠票;如果有多张优惠票满足条件,则优先消耗获得最早的优惠票。现在你得到了小轩最近的公共交通出行记录,你能帮他算算他的花费吗?

输入格式:

第一行包含一个正整数 n,代表乘车记录的数量,接下来的 n 行,每行包含 3 个整数,相邻两数之间以一个空格分隔。第 i 行的第 1 个整数代表第 i 条记录乘坐的交通工具,0 代表地铁,1 代表公交车;第 2个整数代表第 i 条记录乘车的票价 pricei;第三个整数代表第 i 条记录开始乘车的时间 t;(距 0时刻的分钟数)。

我们保证出行记录是按照开始乘车的时间顺序给出的,且不会有两次乘车记录出现在同一分钟。

输出格式:

输出文件有一行,包含一·个正整数,代表小轩出行的总花费。

输入输出样例:

6

``0 10 3`

1 5 46

0 12 50

1 3 96

``0 5 110`

1 6 135

输出:

36

分析问题:

把乘地铁的信息存入优先队列,以时间较早为优先。乘坐公交时,从队列中查找价值大于本次票价且过期时间最早的价值最低的优惠券。

一旦票过期了,就删掉,但是如果票只是价值不够,那可能后面还会有用,所以还要压回去。

编码实现:

#include <bits/stdc++.h>
#include <queue>
#include <stack>
#include <algorithm>
using namespace std;
struct Jt {
	//类型
	int type; //0  1
	//票价
	int price;
	//时间
	int time;  //时间由小到大
	//重载运算符
	bool operator<(const Jt & jt) const { //operator
		if(this->time==jt.time)return this->price>jt.price;
		return this->time>jt.time;
	}
	void show() {
		cout<<this->price<<" "<<this->time;
	}
};

Jt jts[100000];
//次数
int n; //100000
//累加结果
int res;
//优先队列
priority_queue<Jt>  myq;
//临时存储
stack<Jt> mys;

void init() {
	int t,p,s;
	for(int i=1; i<=n; i++) {
		cin>>t>>p>>s;
		jts[i]= {t,p,s};
	}
	//排序
	//sort(jts+1,jts+1+n);

}

void slove() {
	for( int i=1; i<=n; i++ ) {
		if( jts[i].type==0  ) {
			res+=jts[i].price;
			//入阶列
			myq.push(jts[i]);
			continue;
		}
		//公交
		//查找
		bool is=false;
		while( !myq.empty()  ) {
			//队列中有优惠票
			Jt jt=myq.top();
			myq.pop();
			if(  jts[i].price<=jt.price  &&  jts[i].time-jt.time<=45  ) {
				is=true;
				break;
			} else {
				//临时存储
				mys.push(jt);
			}
		}
		if(!is) {
			//不可以抵消
			res+=jts[i].price;
		}
		//重新压入队列
		while(!mys.empty()) {
			myq.push(mys.top());
			mys.pop();
		}
	}
}

int main() {
	//优先队列
	//int 基本类型,自动提供比较
	cin>>n;
	init();
	slove();
	cout<<res<<endl;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一枚大果壳

码文不易,晚上熬夜需要点咖啡钱

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值