蓝桥杯校内选拔赛题

本文精选了八道算法竞赛题目,包括球票大奖赛、冰雹数、立方体翻滚、用户名提取、算式最大值、机器人移动、非法二进制数等,通过详细解析与代码实现,展示了算法设计与优化的精髓。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第一题:赢球票

某机构举办球票大奖赛。获奖选手有机会赢得若干张球票。

主持人拿出 N 张卡片(上面写着 1~N 的数字),打乱顺序,排成一个圆圈。
你可以从任意一张卡片开始顺时针数数: 1,2,3…
如果数到的数字刚好和卡片上的数字相同,则把该卡片收入囊中,从下一个卡片重新数数。
直到再无法收获任何卡片,游戏结束。囊中卡片数字的和就是赢得球票的张数。

比如:
卡片排列是:1 2 3
我们从1号卡开始数,就把1号卡拿走。再从2号卡开始,但数的数字无法与卡片对上,
很快数字越来越大,不可能再拿走卡片了。因此这次我们只赢得了1张球票。

还不算太坏!如果我们开始就傻傻地从2或3号卡片数起,那就一张卡片都拿不到了。

如果运气好,卡片排列是 2 1 3
那我们可以顺利拿到所有的卡片!

本题的目标就是:已知顺时针卡片序列。
随便你从哪里开始数,求最多能赢多少张球票(就是收入囊中的卡片数字之和)

输入数据:

第一行一个整数N(N<100),表示卡片数目
第二行 N 个整数,表示顺时针排列的卡片

输出数据:

一行,一个整数,表示最好情况下能赢得多少张球票

样例输入:

3
1 2 3

样例输出:

1

样例输入:

3
2 1 3

样例输出:

6

代码:

#include <vector>
#include <stack>
#include <string>
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <queue>
#define _for(i,a,b) for(int i=a;i<b;i++)
#define _unfor(i,a,b) for(int i=a;i>=b;i--)
#define RI(a) scanf("%d",&a)
#define mset(a,val,n) for(int i=0;i<n;i++)a[i]=val;
#define mset2(a,val,n,m) for(int i=0;i<n;i++)for(int j=0;j<m;j++)a[i][j]=val;
#define FI freopen("in.txt","r",stdin)
#define FO freopen("out.txt","w",stdout)
using namespace std;
int main() {
    int a[111],n,c,_max=0,sum=0,ans=0;
    RI(n);
    _for(i,0,n) {
        RI(a[i]);
        _max=max(a[i],_max);
    }
    //从第i张卡片开始顺时针数数
    _for(i,0,n) {
        c=1,sum=0;
        vector<int> vec;
        _for(j,0,n)vec.push_back(a[j]);
        vector<int>::iterator pos=vec.begin()+i;//尝试从i开始数
        while(vec.size()) {
            if((*pos)==c) {
                ans=max(ans,sum+=(*pos));//更新全局最大ans
                pos = vec.erase(pos);//删除当前卡片,从下一个重新数(vec.earse会返回下一个迭代器)
                c=1;//重新计数
            } else {
                pos++;
                if(++c>_max)break;//当前数的数大于最大卡片
            }
            if(pos==vec.end())pos=vec.begin();
        }
    }
    printf("%d\n",ans);
}

我学到的:

关于Vector的用法可以参考这篇博客:C++中STL库中的Vector容器

要注意关于Vector中erase的用法(最后传递类似于指针)

pos = vec.erase(pos);     删除当前卡片,从下一个重新数(vec.earse会返回下一个迭代器)

比赛前,将所有头文件写下来,还有特别好用的宏定义一定要学着用


第二题:冰雹数

题目大意:
给你一个数N,如果N为偶数则N=N/2,如果N为奇数N=N*3+1
循环下去问你N最大达到什么位置
比如N=9
9,28,14,7,22,11,34,17,52,26,13,40,20,10,5,16,8,4,2,1
可以看到,N=9的时候,这个“小冰雹”最高冲到了52这个高度

输入格式:

一个正整数N(N<1000000)

输出格式:

一个正整数,表示不大于N的数字,经过冰雹数变换过程中,最高冲到了多少

样例输入:

10

样例输出:

52

样例输入:

100

样例输出:

9232

代码:

#include <vector>
#include <stack>
#include <string>
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <queue>
#define _for(i,a,b) for(int i=a;i<b;i++)
#define _unfor(i,a,b) for(int i=a;i>=b;i--)
#define RI(a) scanf("%d",&a)
#define mset(a,val,n) for(int i=0;i<n;i++)a[i]=val;
#define mset2(a,val,n,m) for(int i=0;i<n;i++)for(int j=0;j<m;j++)a[i][j]=val;
#define FI freopen("in.txt","r",stdin)
#define FO freopen("out.txt","w",stdout)
using namespace std;
typedef long long LL;
 
LL ans=0,N,x;
int main() {
    cin>>N;
    _for(i,1,N+1){
        x=N;
        while(x>1)
            ans=max(ans,x=x%2?(x*3+1):(x/2));
    }
    cout<<ans<<endl;
}

第四题:立方体

小Hi在水平桌面上放置了一个立方体。初始时,上下左右前后6个面的编号依次是1、6、4、3、2、5

现在立方体经过若干次翻滚,每次翻滚是向前翻滚90度(F)、向后翻滚90度(B)、向左翻滚90度(L)、向右翻滚90度(R)之一

请你计算翻滚后上下左右前后6个面的编号依次是多少?

输入

一个由FBLR组成的序列。

长度不超过100。

输出

输出6行,每行一个整数。依次是上下左右前后6个面的编号。

样例输入

LB

样例输出

2
5
1
6
4
3

思路:

看字符串长度不回超过100,所以不用考虑效率,只需要写 向前F,向左L两个函数。然后其他的向后B就等于三次向前,R()=3L(),即把两个函数定义完就行了

代码:

#include <vector>
#include <stack>
#include <string>
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <queue>
#define _for(i,a,b) for(int i=a;i<b;i++)
#define _unfor(i,a,b) for(int i=a;i>=b;i--)
#define RI(a) scanf("%d",&a)
#define mset(a,val,n) for(int i=0;i<n;i++)a[i]=val;
#define mset2(a,val,n,m) for(int i=0;i<n;i++)for(int j=0;j<m;j++)a[i][j]=val;
#define FI freopen("in.txt","r",stdin)
#define FO freopen("out.txt","w",stdout)
using namespace std;
typedef long long LL;

int a[6]={1,6,4,3,2,5};
char s[111];
void F(){
    swap(a[0],a[5]);
    swap(a[5],a[4]);
    swap(a[5],a[1]);
}
void L(){
    swap(a[1],a[3]);
    swap(a[0],a[1]);
    swap(a[1],a[2]);
}
int main() {
    cin>>s;
    int n=strlen(s);
    _for(i,0,n){
        if(s[i]=='L')L();
        if(s[i]=='R'){L();L();L();}
        if(s[i]=='F')F();
        if(s[i]=='B'){F();F();F();}
    }
    _for(i,0,6)cout<<a[i]<<endl;
}

我学到的:

思路在于先创个代表上下左右前后的数组,然后直接对这个数组动手脚(swap)

注意L()和F()函数是在全局变量,且是不带参数的

对于字符串,可以再定义一个数组,然后就可以cin>>这样输进去(我才知道


第五题:提取用户名

在现在的各种互联网应用中,在一段文字中使用’@'字符来提起一名用户是流行的做法。

例如:

“@littleho submitted his code 30 times before he got passed the system test.”

其中littleho就是一个用户名。我们规定在一段文字中,’@'字符之后一段连续的、非空的大小写英文字母组成的字符串被视为提起的用户名。

给定一段文字,请你输出其中所有提到的用户名。

输入

一行文本,只包含大小写字母、标点符号和空格。长度不超过800。

输出

按文本中的顺序输出所有提到的用户名,之间用一个空格隔开。重复提到的相同用户名也重复输出。

样例输入

@abc:@@,@littleho’s code is so confusing. @abc.

样例输出

abc littleho abc

代码:

#include <vector>
#include <stack>
#include <string>
#include <cstring>
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <queue>
#define _for(i,a,b) for(int i=a;i<b;i++)
#define _unfor(i,a,b) for(int i=a;i>=b;i--)
#define RI(a) scanf("%d",&a)
#define mset(a,val,n) for(int i=0;i<n;i++)a[i]=val;
#define mset2(a,val,n,m) for(int i=0;i<n;i++)for(int j=0;j<m;j++)a[i][j]=val;
#define FI freopen("in.txt","r",stdin)
#define FO freopen("out.txt","w",stdout)
using namespace std;
typedef long long LL;

char s[888],buf[888];
bool ischar(char c)
{
    return (c>='A'&&c<='Z')||(c>='a'&&c<='z');
}
 
int main() {
    cin.getline(s,888);
    int n=strlen(s),flag=0;
    _for(i,0,n)                              循环结束的 i 会接着循环
    {
		if(s[i]=='@')
		{
	        int l=++i;             	         使 i 加一,且从右至左赋值,故等号两边值相同
	        while(i<n&&ischar(s[i]))
	            buf[i-l]=s[i++];             避免了使用两次 i++
	        if(strlen(buf))                  判断是否为空
			{
				i--;                         因为再次循环 i++,避免与上面两次增加,故 i--
	            if(flag)cout<<" ";flag=1;
	            cout<<buf;
	        }
	        mset(buf,0,n);                    清空数组
	    }
	}
	return 0;
}

我学到的:

主要思路:
创建两个数组,一个用于接收输入的一行字符串,一个用于输出
用 i 遍历整个字符串数组,从第一个开始判断是否使@,若是,则判断接下来的元素(是不是符合要求),若符合,则用输出数组接收,直到不符合的元素停止接收,开始输出(注意:第一次输出不用加空格,以后每次都要),最后清空数组,再遍历

cin.getline( )是用于获取一行字符串放入参数中的


第六题:算式最大值

给定:

1)N个正整数A1, A2, … AN;

2)P个加号+和Q个减号-; (P+Q=N-1)

3)K对括号()

请你使用全部整数、加减号和括号,组成一个合法的算式(A1~AN在算式中的顺序随意),使得算式的结果最大。

注意加减号只能作为二元运算符出现在算式中,不能作为正负号。

括号可以出现在算式最左和最右,例如(((1+2)))是合法的。

例如对于样例数据,(2-1)+3或3+(2-1)等都是结果最大的算式。

输入

第一行包含4个整数,N,P, Q和K。

第二行包含N个整数A1, A2, … AN。

2 ≤ N ≤ 100 P+Q+1=N 0 ≤ K ≤ 10

1 ≤ Ai ≤ 1000

输出

最大算式结果

样例输入

3 1 1 1
1 2 3

样例输出

4

分析

这道题一开始我只想到用全部大的数减去小的数,没有考虑到括号的问题,现在想想括号也很有用
在这里插入图片描述
比如这题有八个减号,若全部减小的则会得算出负数

现在有一个新思路:
如果存在括号就让减号尽力少,那么就可以这样4 -(1-2-3) = 4 -1+2+3,就变成一个减号了,显然只有一个减号时,就是最大和(前面已经讨论过减号不能全部消去),而且只需要一个括号就可以实现最大值,所以分为三种情况讨论:
在这里插入图片描述

代码:

#include <vector>
#include <stack>
#include <string>
#include <cstring>
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <queue>
#define _for(i,a,b) for(int i=a;i<b;i++)
#define _unfor(i,a,b) for(int i=a;i>=b;i--)
#define RI(a) scanf("%d",&a)
#define mset(a,val,n) for(int i=0;i<n;i++)a[i]=val;
#define mset2(a,val,n,m) for(int i=0;i<n;i++)for(int j=0;j<m;j++)a[i][j]=val;
#define FI freopen("in.txt","r",stdin)
#define FO freopen("out.txt","w",stdout)
using namespace std;
typedef long long LL;
 
int n,p,q,k,a[111],sum=0,subsum=0;
int main()
{
    cin>>n>>p>>q>>k;
    _for(i,0,n)scanf("%d",&a[i]);
    _for(i,0,n)sum+=a[i];
    sort(a,a+n);
    _for(i,0,q)subsum+=a[i];
    if(q)sum-=k?(a[0]*2):subsum*2;
    cout<<sum<<endl;
}

第七题:机器人移动

假设一个机器人在笛卡尔坐标系上。它从(X1, Y1)移动到了(X2, Y2),然后向右转90度,继续前进。

请你计算这个机器人继续前进过程中最先经过的整点是哪一个?

输入

四个整数X1, Y1, X2, Y2。

-1000000 ≤ X1, Y1, X2, Y2 ≤ 1000000 保证(X1, Y1)和(X2, Y2)是不同的点。

输出

两个整数X和Y代表最先经过的整点坐标。

样例输入

0 0 1 2

样例输出

3 1

分析:

一条向量(x,y)第一次经过的整点是坐标除以他们的最大公约数:(x/gcd(x,y),y/gcd(x,y))

代码:

#include <vector>
#include <stack>
#include <string>
#include <cstring>
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <queue>
#define _for(i,a,b) for(int i=a;i<b;i++)
#define _unfor(i,a,b) for(int i=a;i>=b;i--)
#define RI(a) scanf("%d",&a)
#define mset(a,val,n) for(int i=0;i<n;i++)a[i]=val;
#define mset2(a,val,n,m) for(int i=0;i<n;i++)for(int j=0;j<m;j++)a[i][j]=val;
#define FI freopen("in.txt","r",stdin)
#define FO freopen("out.txt","w",stdout)
using namespace std;	
typedef long long LL;
 
int gcd(int a,int b){
    return b==0?a:gcd(b,a%b);
}
 
int main()
{
    int x1,y1,x2,y2;
	cin>>x1>>y1>>x2>>y2;
	int dy=y2-y1,dx=x2-x1,gcdxy=gcd(abs(dx),abs(dy));
	printf("%d %d\n",x2+dy/gcdxy,y2-dx/gcdxy);
}

我学到的:

最后之所以是用 x2+ 和用 y2- ,是因为题目问的是向右九十度后经过的整数点,所以其实是求法线的,法线的斜率于原来的斜率乘积是 -1 ,懂了吧?(好吧,还是不懂,以后再说吧…)
还有,又考初中数学?好吧,还是多刷题吧…


第八题:非法二进制数(未)

如果一个二进制数包含连续的两个1,我们就称这个二进制数是非法的。

小Hi想知道在所有 n 位二进制数(一共有2n个)中,非法二进制数有多少个。

例如对于 n = 3,有 011, 110, 111 三个非法二进制数。

由于结果可能很大,你只需要输出模109+7的余数。
输入

一个整数 n (1 ≤ n ≤ 100)。
输出

n 位非法二进制数的数目模10^9+7的余数。

样例输入

3

样例输出

3

分析:

像这一类计数问题一定要记住不遗漏,不重复。
 
这道题用动态规划计数,用d(i)表示i位非法二进制数的个数,i可以填0和1则分类,d0(i)表示以0结尾的非法数:
 
.分类计数加法原理:d(i)=d0(i)+d1(1);
 
1.  i填0的时候:d0(i)=d(i-1); //因为0不能为前面做出贡献,填0就等于d(i-1)
 
2. i填1的时候,
 
   (1)从不遗漏角度:首先 d1(i)肯定包含了d(i-1),d(i-1)就是看有没有遗漏,比如一个i-1位的数不是非法数但第i位填1就让它变成了非法数,比如1001填1后变成10011,所以这就是d(i-1)遗漏的补分,所以d1(i)就包含了d(i-1)和{i-1位以1为结尾的所有二进制数}
 
   (2)从不重复角度:U1= d(i-1) 和U2={i-1位以1为结尾的所有二进制数}这两个集合 重复部分U1U2=d1(i-1),  
 
  综上i填1的时候,d1(i)= U1 + U2 - d1(i-1) = d0(i-1) + U2 = d0(i-1) + 2^(i-2);

递推式:

i>2时

d[0][i]=d[0][i-1]+d[1][i-1];

d[1][i]=d[0][i-1]+pow(2,i-2);

i=1时 d[0][1]=d[1][1]=0;

i=2时 d[0][2]=0;d[1][2]=1;

最终结果是 d[0][n]+d[1][n];

代码:

#include <vector>
#include <stack>
#include <string>
#include <cstring>
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <queue>
#define _for(i,a,b) for(int i=a;i<b;i++)
#define _unfor(i,a,b) for(int i=a;i>=b;i--)
#define RI(a) scanf("%d",&a)
#define mset(a,val,n) for(int i=0;i<n;i++)a[i]=val;
#define mset2(a,val,n,m) for(int i=0;i<n;i++)for(int j=0;j<m;j++)a[i][j]=val;
#define FI freopen("in.txt","r",stdin)
#define FO freopen("out.txt","w",stdout)
using namespace std;	
typedef long long LL;

LL d[2][111],mod=1000000007,pow2[111];
int main()
{
    int n;
    cin>>n;
    pow2[0]=d[1][2]=1;
    _for(i,1,n)pow2[i]=(pow2[i-1]*2)%mod;
    _for(i,3,n+1){
        d[0][i]=(d[0][i-1]+d[1][i-1])%mod;
        d[1][i]=(d[0][i-1]+pow2[i-2])%mod;
    }
    cout<<(d[0][n]+d[1][n])%mod<<endl;
}

我学到的:

(回头看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值