文章目录
- 题目链接
- B组真题(其余题目同A组真题相同,这里不在列举,已给出A组真题题解链接)
- 题目结构
- 第一题 第几天
- 第二题 明码
- 第四题 测试次数
- 第五题 快速排序
- 第六题 递增三元组
- 第七题 螺旋折线
- 第八题 日志统计
- 第十题 乘积最大
其余题目同A组真题相同,这里不在列举,给出A组真题题解链接:博客链接
题目链接
B组真题(其余题目同A组真题相同,这里不在列举,已给出A组真题题解链接)
题目结构
题目 | 类型 | 分值 |
第一题 | 结果填空 | 5分 |
第二题 | 结果填空 | 7分 |
第三题 | 结果填空 | 13分 |
第四题 | 结果填空 | 17分 |
第五题 | 代码填空 | 9分 |
第六题 | 程序设计 | 11分 |
第七题 | 程序设计 | 19分 |
第八题 | 程序设计 | 21分 |
第九题 | 程序设计 | 23分 |
第十题 | 程序设计 | 25分 |
第一题 第几天
- 问题重现
2000年的1月1日,是那一年的第1天。
那么,2000年的5月4日,是那一年的第几天?
输出
输出一个整数表示答案
- 解题思路
直接统计之间有多少天即可。 - 答案
第二题 明码
- 问题重现
汉字的字形存在于字库中,即便在今天,16点阵的字库也仍然使用广泛。
16点阵的字库把每个汉字看成是16x16个像素信息。并把这些信息记录在字节中。
一个字节可以存储8位信息,用32个字节就可以存一个汉字的字形了。
把每个字节转为2进制表示,1表示墨迹,0表示底色。每行2个字节,
一共16行,布局是:
第1字节,第2字节
第3字节,第4字节
…
第31字节, 第32字节
这道题目是给你一段多个汉字组成的信息,每个汉字用32个字节表示,这里给出了字节作为有符号整数的值。
题目的要求隐藏在这些信息中。你的任务是复原这些汉字的字形,从中看出题目的要求,并根据要求填写答案。
输入
无输入,所给信息为(一个10个汉字):
4 0 4 0 4 0 4 32 -1 -16 4 32 4 32 4 32 4 32 4 32 8 32 8 32 16 34 16 34 32 30 -64 0 16 64 16 64 34 68 127 126 66 -124 67 4 66 4 66 -124 126 100 66 36 66 4 66 4 66 4 126 4 66 40 0 16 4 0 4 0 4 0 4 32 -1 -16 4 32 4 32 4 32 4 32 4 32 8 32 8 32 16 34 16 34 32 30 -64 0 0 -128 64 -128 48 -128 17 8 1 -4 2 8 8 80 16 64 32 64 -32 64 32 -96 32 -96 33 16 34 8 36 14 40 4 4 0 3 0 1 0 0 4 -1 -2 4 0 4 16 7 -8 4 16 4 16 4 16 8 16 8 16 16 16 32 -96 64 64 16 64 20 72 62 -4 73 32 5 16 1 0 63 -8 1 0 -1 -2 0 64 0 80 63 -8 8 64 4 64 1 64 0 -128 0 16 63 -8 1 0 1 0 1 0 1 4 -1 -2 1 0 1 0 1 0 1 0 1 0 1 0 1 0 5 0 2 0 2 0 2 0 7 -16 8 32 24 64 37 -128 2 -128 12 -128 113 -4 2 8 12 16 18 32 33 -64 1 0 14 0 112 0 1 0 1 0 1 0 9 32 9 16 17 12 17 4 33 16 65 16 1 32 1 64 0 -128 1 0 2 0 12 0 112 0 0 0 0 0 7 -16 24 24 48 12 56 12 0 56 0 -32 0 -64 0 -128 0 0 0 0 1 -128 3 -64 1 -128 0 0
输出
根据题意输出正确答案
- 解题思路
一个字节位。我们只要将这些十进制整数转换为
位二进制表示即可,注意负数的补码需要除符号位按位取反再
,之后按照位为
打印’.’,位为
打印’ '即可。最后依次打印得到的信息为:“9的9次方等于多少?”。
- 代码
/**
*@filename:明码
*@author: pursuit
*@created: 2021-04-08 18:29
**/
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;
int a[34];
int bin[8];
void change(int value){
//转二进制。
int temp=abs(value);
for(int i=0;i<8;i++){
bin[i]=temp%2;
temp/=2;
}
if(value<0){
//若是负数,则需出符号位外全部取反再加1.
bin[7]=1;
for(int i=0;i<7;i++){
bin[i]=!bin[i];
}
bin[0]+=1;
for(int i=1;i<7;i++){
bin[i]+=bin[i-1]/2;
bin[i-1]%=2;
}
}
}
void solve(){
for(int i=0;i<32;i++){
change(a[i]);
for(int j=7;j>=0;j--){
bin[j]?cout<<"1":cout<<" ";
}
if(i%2)cout<<endl;
}
}
int main(){
/* while(true){
for(int i=0;i<32;i++){
cin>>a[i];
}
solve();
} */
cout<<ll(pow(9,9))<<endl;//387420489
return 0;
}
- 答案
第四题 测试次数
- 问题重现
x星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机。
各大厂商也就纷纷推出各种耐摔型手机。x星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后才允许上市流通。
x星球有很多高耸入云的高塔,刚好可以用来做耐摔测试。
塔的每一层高度都是一样的,与地球上稍有不同的是,他们的第一层不是地面,而是相当于我们的2楼。
如果手机从第7层扔下去没摔坏,但第8层摔坏了,则手机耐摔指数=7。
特别地,如果手机从第1层扔下去就坏了,则耐摔指数=0。
如果到了塔的最高层第n层扔没摔坏,则耐摔指数=n
为了减少测试次数,从每个厂家抽样3部手机参加测试。
某次测试的塔高为1000层,如果我们总是采用最佳策略,在最坏的运气下最多需要测试多少次才能确定手机的耐摔指数呢?
输出
输出一个整数表示答案
- 解题思路
我们试想,如果只有一个手机,那么直接暴力枚举就是答案了。可是现在有三个手机。二分?可以做吗?我们发现,对于二分法,如果每次枚举的点都碎了,那就无法得到答案了,显然不行,对于有无限个手机的时候这个才是最佳决策。那么这道题我们该怎么做呢?其实这道题蕴含着好多决策点,我们测一次楼层的时候即是一个状态,而且这状态显然是相互转移的,所以动态规划才是解决这道问题的关键。我们用表示现在还有
部手机且待测楼层有
层时,最坏运气下的最少的测试次数。那么我们测试
层时则有两种情况:
- 如果当前测的手机坏了,那么手机数量减一,并去下一层测试,此时待测楼层为
。
- 如果当前测的手机没坏,那么就去上一层测试,此时
。
我们想要的状态为,我们已知的状态即是只有一个手机的时候,我们运气最差只能从
测到
,这样进行状态转移即可。
- 代码
/**
*@filename:测试次数
*@author: pursuit
*@created: 2021-04-08 20:05
**/
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;
int dp[4][1005];//dp[i][j]表示现在还有i部手机,且待测楼层有j时,最坏运气下的最少的测试次数。
void solve(){
}
int main(){
for(int i=1;i<=3;i++){
for(int j=1;j<=1000;j++){
//无论有几部手机,运气最差的测试次数就是楼层的高度。
dp[i][j]=j;
}
}
//接下来开始模拟,对于只有一个手机的时候,我们无法再进行优化,因为只有一个手机我们只能从第一层测到后面。
for(int i=2;i<=3;i++){
//开始模拟有两个手机的时候。
for(int j=1;j<=1000;j++){
//对于这些状态。在j楼层的状态转移情况。
for(int k=1;k<j;k++){
//在j楼层的时候1~j-1层的测试情况,若摔坏了,则手机数量减一,下次去楼下测试即可,最多测试k-1次。
//若没摔坏,则手机的数量不变,下一次要去楼上测试,最多再测试j-k次。
dp[i][j]=min(dp[i][j],max(dp[i-1][k-1],dp[i][j-k])+1);
}
}
}
cout<<dp[3][1000]<<endl;
return 0;
}
第五题 快速排序
- 问题重现
以下代码可以从数组a[]中找出第k小的元素。
它使用了类似快速排序中的分治算法,期望时间复杂度是O(N)的。
请仔细阅读分析源码,填写划线部分缺失的内容。
#include <stdio.h>
#include<stdlib.h>
int quick_select(int a[], int l, int r, int k) {
int p = rand() % (r - l + 1) + l;//生成一个范围在[l,r]之间的。
int x = a[p];//随机选取了下标与其进行交换。
{int t = a[p]; a[p] = a[r]; a[r] = t;}
int i = l, j = r;//此时j代表的值是x,原来p的值为a[r]。
while(i < j) {
//寻找第一个大于等于x的下标。
while(i < j && a[i] < x) i++;
//如果找到了,那么就将值传给a[j]。实际上就是以x为参照,选择一个比x大的数,放到高位。
if(i < j) {
a[j] = a[i];
j--;
}
//找到最后一个小于等于x的下标。
while(i < j && a[j] > x) j--;
//如果找到了就交换这值。
if(i < j) {
a[i] = a[j];//选择一个比x小的数,放到低位。
i++;
}
}
a[i] = x;
p = i;
if(i - l + 1 == k) return a[i];//说明区间
//if(i - l + 1 < k) return quick_select( _____________________________ ); //填空
if(i - l + 1 < k) return quick_select(a,i+1,r,k-i+l-1); //填空
else return quick_select(a, l, i - 1, k);
}
int main()
{
int a[] = {1, 4, 2, 8, 5, 7, 23, 58, 16, 27, 55, 13, 26, 24, 12};
printf("%d\n", quick_select(a, 0, 14, 5));
for(int i=0;i<=14;i++){
printf("%d ",a[i]);
}
printf("\n");
return 0;
}
- 解题思路
解决这道题首先得理解快排的划分做法,就是用两个指针来将数组划分为三个区间,然后确认
左边都小于当前选定的参照,
右边都大于当前选定的参照,这道题前面选定的参照是通过随机函数来实现的,选定的范围为
,接下来实际上就是快排的递归做法了。先了解这个快排函数参数的作用:
表示数组不变 ,
表示左指针下标边界,
表示右指针下标边界 ,
表示选择第k小的元素。在函数的最后,就是递归了,
表示的就是
这段区间,其中的值都是小于选定参照的,而
这些都是比参照值要大的,所以:如果
比k大,说明要在
~
中找;还是找第
个元素 如果
-
比
小,说明要在
~
某个值中找,我们看还需要找到新一轮递归中找第多少小的元素,这里新参数
就等于 原k减去当前一轮的
~
的个数 即
-
。
- 答案
k-(i-l+1)
第六题 递增三元组
- 问题重现
给定三个整数数组
$A = [A1, A2, … AN], $
请你统计有多少个三元组(i, j, k) 满足:
- $ Ai < Bj < Ck$
输入
第一行包含一个整数N。
第二行包含N个整数
第三行包含N个整数
第四行包含N个整数
输出
一个整数表示答案
样例输入
3
1 1 1
2 2 2
3 3 3
样例输出
27
- 解题思路
这道题有三种做法。三种做法的共同点就是先确定,然后从数组
中找出小于
的元素数量,从数组
中找出大于
的元素数量。第一种做法就是二分,将数组
和数组
升序排列,然后遍历
数组累加即可。第二种做法就是双指针,这种做法就是要将这三个数组都升序排列,这样我们每次移动获得的对下一次都有效,因为
也是有序增加的。第三种做法就是将
和
的值分别用一个前缀数组记录,其中pre[i]就表示了小于等于
的数有多少个。这样处理之后每次只要累加即可,不用去查找,效率最高!
- 二分代码
/**
*@filename:递增三元组
*@author: pursuit
*@created: 2021-04-08 20:22
**/
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;
int n;
int a[maxn],b[maxn],c[maxn];
void solve(){
sort(a,a+n);
sort(c,c+n);
ll ans=0;
ll index1,index2;
ll l,r,mid;
for(int i=0;i<n;i++){
//二分查找数组a中第一个大于等于b[i]的下标。
l=0,r=n;
while(l<r){
mid=(l+r)>>1;
if(a[mid]>=b[i])r=mid;
else l=mid+1;
}
index1=r;
//二分查找数组c中第一个大于b[i]的下标。
l=0,r=n;
while(l<r){
mid=(l+r)>>1;
if(c[mid]>b[i])r=mid;
else l=mid+1;
}
index2=l;
ans+=index1*(n-index2);
//cout<<index1<<" "<<i<<" "<<index2<<endl;
}
cout<<ans<<endl;
}
int main(){
while(cin>>n){
for(int i=0;i<n;i++)cin>>a[i];
for(int i=0;i<n;i++)cin>>b[i];
for(int i=0;i<n;i++)cin>>c[i];
//排序之后二分寻找答案。
solve();
}
return 0;
}
- 双指针代码
/**
*@filename:递增三元组双指针
*@author: pursuit
*@created: 2021-04-09 11:10
**/
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;
int n;
int a[maxn],b[maxn],c[maxn];
int pre_cnta[maxn],pre_cntc[maxn];
void solve(){
for(int i=1;i<maxn;i++){
pre_cnta[i]=pre_cnta[i-1]+pre_cnta[i];
}
for(int i=1;i<maxn;i++){
pre_cntc[i]=pre_cntc[i-1]+pre_cntc[i];
}
ll ans=0;
//pre_cntc[i]表示的是小于等于i的所有数。
for(int i=0;i<n;i++){
ans+=(ll)pre_cnta[b[i]-1]*(pre_cntc[maxn-1]-pre_cntc[b[i]]);
}
cout<<ans<<endl;
}
int main(){
while(cin>>n){
memset(pre_cnta,0,sizeof(pre_cnta));
memset(pre_cntc,0,sizeof(pre_cntc));
for(int i=0;i<n;i++){
cin>>a[i];
//统计a[i]出现的次数。
pre_cnta[a[i]]++;
}
for(int i=0;i<n;i++)cin>>b[i];
for(int i=0;i<n;i++){
cin>>c[i];
//统计c[i]出现的次数。
pre_cntc[c[i]]++;
}
solve();
}
return 0;
}
- 前缀和代码
/**
*@filename:递增三元组双指针
*@author: pursuit
*@created: 2021-04-09 11:10
**/
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;
int n;
int a[maxn],b[maxn],c[maxn];
int pre_cnta[maxn],pre_cntc[maxn];
void solve(){
for(int i=1;i<maxn;i++){
pre_cnta[i]=pre_cnta[i-1]+pre_cnta[i];
}
for(int i=1;i<maxn;i++){
pre_cntc[i]=pre_cntc[i-1]+pre_cntc[i];
}
ll ans=0;
//pre_cntc[i]表示的是小于等于i的所有数。
for(int i=0;i<n;i++){
ans+=(ll)pre_cnta[b[i]-1]*(pre_cntc[maxn-1]-pre_cntc[b[i]]);
}
cout<<ans<<endl;
}
int main(){
while(cin>>n){
memset(pre_cnta,0,sizeof(pre_cnta));
memset(pre_cntc,0,sizeof(pre_cntc));
for(int i=0;i<n;i++){
cin>>a[i];
//统计a[i]出现的次数。
pre_cnta[a[i]]++;
}
for(int i=0;i<n;i++)cin>>b[i];
for(int i=0;i<n;i++){
cin>>c[i];
//统计c[i]出现的次数。
pre_cntc[c[i]]++;
}
solve();
}
return 0;
}
第七题 螺旋折线
- 问题重现
如图所示的螺旋折线经过平面上所有整点恰好一次。
对于整点(X, Y),我们定义它到原点的距离dis(X, Y)是从原点到(X, Y)的螺旋折线段的长度。
例如dis(0, 1)=3, dis(-2, -1)=9
给出整点坐标(X, Y),你能计算出dis(X, Y)吗?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ACBfryn2-1618120461726)(第九届蓝桥杯(省赛)C++B组真题题解.assets/20191117235422_15478.png)]
输入
X和Y,数据在int范围以内。
输出
输出dis(X, Y)
样例输入
0 1
样例输出
3
- 解题思路
这种题目给出来一定是找规律做的。我们可以先对对该图形进行一些处理。如下:
这样处理之后相当于是从内部正方形的左下角出发,转一圈后到外层正方形的左下角再出发。这样的好处就是我们可以对这些点进行分类,如在哪层正方形的哪条边上。首先先分析相邻正方形之间的周长关系,为倍的关系。那么我们将坐标点分为
块,上边下边左边和右边。对于上边,其坐标点满足
,位于
轴正方向上的点,它正好就是
,那么以该点为基点,其余点就是以
作为偏移量。同理,对于下边确定在
轴负方向上的点,为
,对于左边确定在
轴负方向上的点为
,对于右边确定在
轴正方向上的点为
。
- 代码
/**
*@filename:螺旋折线
*@author: pursuit
*@created: 2021-04-09 12:04
**/
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;
ll x,y;
void solve(){
}
int main(){
while(cin>>x>>y){
int judge=max(abs(x),abs(y));//第几个矩形处。
//判断是在哪一条边上。
if(abs(x)<=y){
//在上方。我们知道在y轴上的点即为(4*y*y-y);
cout<<4*y*y-y+x<<endl;
}
else if(abs(y)<=x){
//在右方,我们知道在x轴上的点即为(4*x*x+x);
cout<<4*x*x+x-y<<endl;
}
else if(abs(x)<=abs(y)&&y<0){
//在下方,我们知道在y轴上的点即为(4*y*y+3*abs(y));
cout<<4*y*y+3*abs(y)-x<<endl;
}
else{
//在左方。我们知道在x轴上的点即为。(4*x*x-3*abs(x));
cout<<4*x*x-3*abs(x)+y<<endl;
}
}
solve();
return 0;
}
第八题 日志统计
- 问题重现
小明维护着一个程序员论坛。现在他收集了一份"点赞"日志,日志共有N行。
其中每一行的格式是:ts id。表示在ts时刻编号id的帖子收到一个"赞"。
现在小明想统计有哪些帖子曾经是"热帖"。
如果一个帖子曾在任意一个长度为D的时间段内收到不少于K个赞,小明就认为这个帖子曾是"热帖"。
具体来说,如果存在某个时刻T满足该帖在[T, T+D)这段时间内(注意是左闭右开区间)收到不少于K个赞,该帖就曾是"热帖"。
给定日志,请你帮助小明统计出所有曾是"热帖"的帖子编号。
输入
第一行包含三个整数N、D和K。
以下N行每行一条日志,包含两个整数ts和id。
1 <= K <= N <= 100000 0 <= ts <= 100000 0 <= id <= 100000
输出
按从小到大的顺序输出热帖id。每个id一行。
样例输入
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3
样例输出
1
3
- 解题思路
这道题我们对这些日志信息按时间排序即可,然后利用双指针滑动统计热帖。注意赞数的更新。 - 代码
/**
*@filename:日志统计
*@author: pursuit
*@created: 2021-04-09 13:37
**/
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;
int n,d,k;
pair<int,int> logInfo[maxn];
bool idInfo[maxn];//idInfo[i]表示id为i的是否曾是热帖。
int cnt[maxn];//cnt[i]表示i在当前的t时间段内获赞的次数。
void solve(){
//排序遍历。
sort(logInfo,logInfo+n);
memset(idInfo,false,sizeof(idInfo));//初始化默认全不是热帖。
memset(cnt,0,sizeof(cnt));//初始无人获赞。
int f=0,t=0;//首尾指针。f表示起点,t表示终点。
while(t<n){
cnt[logInfo[t].second]++;//获得赞数。
while(logInfo[t].first-logInfo[f].first>=d){
//当时间间隔超过了d,那么之前的赞就要作废。
cnt[logInfo[f].second]--;
f++;
}
if(cnt[logInfo[t].second]>=k){
idInfo[logInfo[t].second]=true;
}
t++;//时间更新。
}
for(int i=0;i<=maxn;i++){
if(idInfo[i])cout<<i<<endl;
}
}
int main(){
while(cin>>n>>d>>k){
for(int i=0;i<n;i++){
cin>>logInfo[i].first>>logInfo[i].second;
}
solve();
}
return 0;
}
第十题 乘积最大
- 问题重现
给定N个整数A1, A2, … AN。请你从中选出K个数,使其乘积最大。
请你求出最大的乘积,由于乘积可能超出整型范围,你只需输出乘积除以1000000009的余数。
注意,如果X<0, 我们定义X除以1000000009的余数是负(-X)除以1000000009的余数。
即:0-((0-x) % 1000000009)
输入
第一行包含两个整数N和K。
以下N行每行一个整数Ai。
1 <= K <= N <= 100000 -100000 <= Ai <= 100000
输出
一个整数,表示答案。
样例输入
5 3
-100000
-10000
2
100000
10000
样例输出
999100009
- 解题思路
使劲贪就可以, 我们来分析一下,按照贪心,我们总想选择乘积最大的。如果k为偶数,那么我们就可以分成k/2对,乘积最大的就是在首尾两处,所以我们可以双指针扫描。如果k为奇数,那么我们就可以先取最右边最大的那一个,这样k就又变为偶数了,我们照常处理。由于存在负数,故注意考虑一种情况就是全为负数的时候,这个时候需要更改符号位即可。 - 代码
/**
*@filename:乘积最大
*@author: pursuit
*@created: 2021-04-10 19:49
**/
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
const ll mod = 1000000009;
int n,k;
ll a[maxn];
void solve(){
sort(a,a+n);
ll result=1;
int sign=1;//符号标志。
if(k%2){
result=a[n-1],k--,n--;
if(result<0){
sign=-1;
}
}
int l=0,r=n-1;
while(k){
ll temp1=a[l]*a[l+1],temp2=a[r]*a[r-1];
if(temp1*sign>=temp2*sign){
result=temp1%mod*result%mod;
l+=2;
}
else{
result=temp2%mod*result%mod;
r-=2;
}
k-=2;
}
cout<<result%mod<<endl;
}
int main(){
while(cin>>n>>k){
for(int i=0;i<n;i++){
cin>>a[i];
}
solve();
}
return 0;
}