本文对 2019、2020、2021、2022 4
年来 CSP_J
复赛的笔试题目以横向维度进行比较,希望对参加复赛的学生有帮助。本文在讲解每一道题目时,仅提供题目的基本要求,更多细节,请自行查阅其它有关文档。
题目:
乘方pow
。
题目描述:
小文同学刚刚接触了信息学竞赛,有一天她遇到了这样一个题:给定正整数a
和b
,求a
b 的值是多少。a
b即b
个a
相乘的值,例如2
3
即为3
个2
相乘,结果为2x2x2=8
。
“简单!”小文心想,同时很快就写出了一份程序,可是测试时却出现了错误。
小文很快意识到,她的程序里的变量都是int
类型的。在大多数机器上,int
类型能表示的最大数为231-1,因此只要计算结果超过这个数,她的程序就会出现错误。由于小文刚刚学会编程,她担心使用int
计算会出现问题。因此她希望你在a
b的值超过109时,输出一个 -1
进行警示,否则就输出正确的ab的值。
然而小文还是不知道怎么实现这份程序,因此她想请你帮忙。
分析问题:
第一道题目属于送分题,一般都不难,要说简单也不简单,注意细节把控,认真审查题目的每一个要求。
本题确实简单,就是一个求幂的运算,本质就是一个累乘算法。但是如果直接使用 pow
函数,肯定是不行的。在给定 a、b
的情况下,使用 pow
函数要么得到正确结果,要么就直接溢出。很明显,此题是要求找到溢出的临界点。
在解决问题时,需要站在数学角度,把一些特殊情况考虑进去。如 a=1、b=1
时。
编码实现:
#include
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;
}
题目:
分糖果
题目描述:
红太阳幼儿园有 n
个小朋友,你是其中之一。保证 n>=2
。有一天你在幼儿园的后花园里发现无穷多颗糖果,你打算拿一些糖果回去分给幼儿 园的小朋友们。 由于你只是个平平无奇的幼儿园小朋友,所以你的体力有限,至多只能拿 R
块糖回去。
但是拿的太少不够分的,所以你至少要拿 L
块糖回去保证 n<=L<=R
。也就是说,如果你拿了 k
块糖,那么你需要保证 L<=k<=R
。
如果你拿了 k
块糖,你将把这 k
块糖放到篮子里,并要求大家按照如下方案分糖果:
只要篮子里有不少于n
块糖果,幼儿园的所有 n
个小朋友(包括你自己)都从篮子中拿走恰好一块糖直到篮子里的糖数量少于n
块。此时篮子里剩余的糖果均归你所有,这些糖果是作为你搬糖果的奖励.。
作为幼儿园高质量小朋友,你希望让作为你搬糖果的奖励的糖果数量(而不是你最后获得的总糖果数量!)尽可能多;因此你需要写一个程序,依次输入n,L,R
,并输出你最多能获得多少作为你搬糖果的奖励的糖果数量。
分析题目
此题本质是求 n
余数问题。因为 n
的最大余数是 n-1
,也就是要在L
的R
之间查找一个 k
看是否满足其除以 n
的余数接近或等于n-1
。
编码实现:
思路一:
可以先计算一个x
值,让其满足L<=x*n<=R
。如果有满足其要求的值,则最大值一定是 n-1
。如果不存在,则最大值为 r
。针对不同的L
和R
,K
可能会有多个,注意本题不是求 K
,而是求 k
除 n
的最大余数。
#include
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<
思路二:
#include
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,一定可以选n-1那个作为余
cout << n-1;
return 0;
题目:
优秀的拆分
题目描述:
一般来说,一个正整数可以拆分成若干个正整数的和。例如, 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
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<
题目:
数字游戏
问题描述:
小K
同学向小P
同学发送了一个长度为8
的01
字符串来玩数字游戏,小P
同学想要知道字符串中究竟有多少个1
。
注意:01
字符串为每一个字符是0
或者1
的字符串,如“101”
不含双引号)为一个长度为3
的01
字符串。
分析问题:
本题目很简单,因为长度固定在8
,直接循环字符串即可得到最终答案。还有一种更简单的方案,使用位运算符。
编程实现:
总感觉下面的代码就点傻,缺少点灵性。一道竞赛题目不至于这么简单,而实际上就是这么简单。
#include
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;
}
题目:
解密 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<=10
5
,对于任意的 1<=i<=k,1<=n
i<=1018,1<=ei X di<=10
18,1<=m<=109。
分析问题:
根据已知条件推导后,可知就是根据已知的系数,求解一元二次方程式的解。
#include
#include
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)<<" "<
题目:
插入排序
问题描述:
插入排序是一种非常常见且简单的排序算法。小 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
个元素,也就是 a
x 的值,修改为 v
。保证 1<=x<=n,1<=v <=109。这种操作会改变数组的元素,修改得到的数组会被保留,也会影响后续的操作。
2x:这是第二种操作,假设 H
老师按照上面的伪代码对 a
数组进行排序,你需要告诉 H
老师原来a
的第 x
个元素,也就是 ax,在排序后的新数组所处的位置。保证 1<=x<=n
。注意这种操作不会改变数组的元素,排序后的数组不会被保留也不会影响后续的操作。
分析问题:
结构化原数组中的数据,每一个数据当前数组中的位置为唯一编号,排序后,使用此唯一编号存储排序后的位置。
编码实现:
#include
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>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=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<
题目:
直播获奖
题目描述:
NOI2130
即将举行。为了增加观赏性,CCF
决定逐一评出每个选手的成绩,并直播即时的获奖分数线。本次竞赛的获奖率为w%
,即当前排名前 w%
的选手的最低成绩就是即时的分数线。
更具体地,若当前已评出了p
个选手的成绩,则当前计划获奖人数为max(1,[p x w%])
,其中w
是获奖百分比,[x]
表示对x向下取整,max(x,y)
表示x
和y
中较大的数。如有选手成绩相同,则所有成绩并列的选手都能获奖,因此实际获奖人数可能比计划中多。
作为评测组的技术人员,请你帮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
#include
#include
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;
}
题目:
公车换乘
问题描述:
著名旅游城市 B
市为了鼓励大家采用公共交通方式出行,推出了一种地铁换乘公交车的优惠方案:
在搭桑一次地铁后可以获得一张优惠票,有效期为 45
分钟,在有效期内可以消耗这张优惠票,免费搭乘一次票价不超过地铁票价的公交车。在有效期内指开始乘公交车的时间与开始乘地铁的时间之差小于等于 45
分钟,即;
t
bus - t
subway ≤ 45
搭乘地铁获得的优惠票可以累积,即可以连续搭乘若干次地铁后再连续使用优惠票搭乘公交车。
搭乘公交车时,如果可以使用优惠票一定会使用优惠票;如果有多张优惠票满足条件,则优先消耗获得最早的优惠票。现在你得到了小轩最近的公共交通出行记录,你能帮他算算他的花费吗?
输入格式:
第一行包含一个正整数 n
,代表乘车记录的数量,接下来的 n
行,每行包含 3
个整数,相邻两数之间以一个空格分隔。第 i
行的第 1
个整数代表第 i
条记录乘坐的交通工具,0
代表地铁,1
代表公交车;第 2
个整数代表第 i
条记录乘车的票价 price
i;第三个整数代表第 i
条记录开始乘车的时间 t
;(距 0
时刻的分钟数)。
我们保证出行记录是按照开始乘车的时间顺序给出的,且不会有两次乘车记录出现在同一分钟。
输出格式:
输出文件有一行,包含一·个正整数,代表小轩出行的总花费。
输入输出样例:
6
``0 10 3`
1 5 46
0 12 50
1 3 96
``0 5 110`
1 6 135
输出:
36
分析问题:
把乘地铁的信息存入优先队列,以时间较早为优先。乘坐公交时,从队列中查找价值大于本次票价且过期时间最早的价值最低的优惠券。
一旦票过期了,就删掉,但是如果票只是价值不够,那可能后面还会有用,所以还要压回去。
编码实现:
#include
using namespace std;
const int N = 1e5+10;
typedef long long ll;
struct Node {
int ty;
ll t,price;
bool operator < (const Node& rhs) const {
if(t == rhs.t) return price > rhs.price;
return t > rhs.t;
}
} rd[N];
int n;
priority_queue q;
stack s;
void solve() {
sort(rd+1,rd+1+n);
ll res = 0;
for(int i = n; i >= 1; i--) {
if(!rd[i].ty) {
res += rd[i].price, q.push(rd[i]);
continue;
}
bool flag = true;
while(q.size()) {
Node x = q.top();
q.pop();
if(rd[i].t - x.t > 45) continue;
if(x.price < rd[i].price) {
s.push(x);
continue;
}
flag = false;
break;
}
if(flag) res += rd[i].price;
while(s.size()) q.push(s.top()), s.pop();
}
printf("%lld\n",res);
}
int main() {
scanf("%d",&n);
for(int i = 1; i <= n; i++)
scanf("%d%d%d",&rd[i].ty,&rd[i].price,&rd[i].t);
solve();
return 0;
}