蓝桥杯2018年国赛试题解题报告

1.换零钞


x星球的钞票的面额只有:100元,5元,2元,1元,共4种。小明去x星旅游,他手里只有2张100元的x星币,太不方便,恰好路过x星银行就去换零钱。小明有点强迫症,他坚持要求200元换出的零钞中2元的张数刚好是1元张数的10倍,剩下的当然都是5元面额的。银行的工作人员有点为难,你能帮助算出:在满足小明要求的前提下,最少要换给他多少张钞票吗?(5元,2元,1元面额的必须都有,不能是0) **思路**:直接模拟即可,注意是求最少的钞票数,**不要只算某种钞票**
//5元
for(int i=1; i*5<=n; i++) {
    //1元
    for(int j=1; j<=n; j++) {
	//2元
	int k=j*10;
	if(i*5+j+k*2==n) {
	    printf("%d+%d+%d=%d\n",j,k,i,i+j+k);
	}
    }
}

2.激光样式

x星球的盛大节日为增加气氛,用30台激光器一字排开,向太空中打出光柱。安装调试的时候才发现,不知什么原因,相邻的两台激光器不能同时打开!国王很想知道,在目前这种bug存在的情况下,一共能打出多少种激光效果?
显然,如果只有3台激光器,一共可以成5种样式,即:
全都关上(sorry, 此时无声胜有声,这也算一种)
开一台,共3种
开两台,只1种
30台就不好算了,国王只好请你帮忙了。要求提交一个整数,表示30台激光器能形成的样式种数。
思路:
1.DFS

int dfs(int now,int lim) {
	if(now==lim)	return 1;
	int res=0;
	if(now==0||!vis[now-1]) {
            vis[now]=1;
	    res+=dfs(now+1,lim);
	}
	vis[now]=0;
	res+=dfs(now+1,lim);
	return res;
}

2.位运算

if(n<=2)	n=1<<n;
else {
	for(int i=n-1; i>=0; i-=2)    maxn+=(1<<i); //101010...
	n=maxn;
}
while(n>0) {
	if((n&(n>>1))==0)    res++;
	n--;
}

3.格雷码

格雷码是以n位的二进制来表示数。与普通的二进制表示不同的是,它要求相邻两个数字只能有1个数位不同,首尾两个数字也要求只有1位之差。
有很多算法来生成格雷码,以下是较常见的一种:
从编码全0开始生成,当产生第奇数个数时,只把当前数字最末位改变(0变1,1变0);当产生第偶数个数时,先找到最右边的一个1,把它左边的数字改变。用这个规则产生的4位格雷码序列如下:
(略)
以下是实现代码,仔细分析其中逻辑,并填写划线部分缺少的代码。

#include <stdio.h>
void show(int a, int n) {
	int msk = 1 << (n-1);
	for (int i = 0; i < n; i++) {
		putchar((a & msk) ? '1' : '0');
		msk = msk >> 1;
	}
	putchar('\n');
}

void f(int n) {
	int num = 1 << n;
	int a = 0;
	for (int i = 0; i < num; i++) {
		show(a, n);
		if (i % 2 == 0) {
			a = a ^ 1;
		} else {
			// a = _________________________ ; //填空
		}
	}
}

int main() {
	f(4);
	return 0;
}

思路:先了解异或运算的一个性质:将一个整数的某位异或1实现反转该位,异或0实现保留该位。取出最低位的前一位(a&(-a))<<1,让a与它异或即实现只有最低位前一位改变。

4.调手表(15分)

小明买了块高端大气上档次的电子手表,他正准备调时间呢。在M78星云,时间的计量单位和地球上不同,M78星云的一个小时有n分钟。
大家都知道,手表只有一个按钮可以把当前的数加一。在调分钟的时候,如果当前显示的数是0,那么按一下按钮就会变成1,再按一次变成2。如果当前的数是n-1,按一次后会变成0。
作为强迫症患者,小明一定要把手表的时间调对。如果手表上的时间比当前时间多1,则要按n-1次加一按钮才能调回正确时间。小明想,如果手表可以再添加一个按钮,表示把当前的数加 k 该多好啊……他想知道,如果有了这个+k按钮,按照最优策略按键,从任意一个分钟数调到另外任意一个分钟数最多要按多少次。
注意,按+k 按钮时,如果加k后数字超过n-1,则会对n取模。比如,n=10, k=6的时候,假设当前时间是0,连按2次+k 按钮,则调为2。
输入格式:一行两个整数 n, k,意义如题。
输出格式:一行一个整数,表示按照最优策略按键,从一个时间调到另一个时间最多要按多少次。
样例输入:5 3
样例输出:2
样例解释:如果时间正确则按0次。否则要按的次数和操作系列之间的关系如下:
1:+1
2:+1, +1
3:+3
4:+3, +1
数据范围
对于30%的数据,0<k<n<=5
对于60%的数据,0<k<n<=100
对于100%的数据,0<k<n<=100000

我的思路:对于这题,我一开始是想到DP:dp[i]=1+min(dp[i-1],dp[(i-k+n)%n]),但很显然这个方程无论怎么执行都会出现用到没更新过的值的情况。
不难发现只要使用k分钟的次数确定下来,则执行顺序不影响结果。因此可以转换思路:枚举分钟数i,对于i>=k的部分仍采用DP,但对于小于k的部分,则枚举它们使用多少个k分钟。

for(int i=1; i<k; i++) {
	dp[i]=i;
        //假设从0分开始
	for(int j=i/k; j<i; j++) {
             //(j*k)%n=使用j次k分钟后的时间。
             //i-(j*k)%n=使用j次k分钟后,与目标还差几分钟。可能为负数。
	     dp[i]=min(dp[i],(i-(j*k)%n+n)%n+j);
	}
	//printf("%d\n",dp[i]);
	res=max(res,dp[i]);
}
for(int i=k; i<n; i++) {
	dp[i]=min(dp[i-1],dp[i-k])+1;
	//printf("%d\n",dp[i]);
	res=max(res,dp[i]);
}

但这种做法只能过60%。

参考答案:可以使用BFS,确保每一分钟都只被访问一次即可。

void bfs(int n,int k) {
//-1表示未访问过
	fill(a+1,a+n,-1);
	queue<int> q;
	q.push(0);
	while(!q.empty()){
		int t=q.front();
		q.pop();
		//向前调1分钟和k分钟后的时间
		int x=(t+1)%n,y=(t+k)%n;
		//每分钟都仅访问一遍,确保每分钟的步长都是最短的
		if(a[x]<0) {
			a[x]=a[t]+1;
			q.push(x);
		}
		if(a[y]<0) {
			a[y]=a[t]+1;
			q.push(y);
		}
	}
}

5.搭积木(15分)

小明对搭积木非常感兴趣,他的积木都是同样大小的正立方体。在搭积木时,小明选取 m块积木作为地基,将他们在桌子上一字排开,中间不留空隙,并称其为第0层。随后,小明可以在上面摆放第1层,第2层,……,最多摆放至第n层。摆放积木必须遵循三条规则:
规则1:每块积木必须紧挨着放置在某一块积木的正上方,与其下一层的积木对齐;
规则2:同一层中的积木必须连续摆放,中间不能留有空隙;
规则3:小明不喜欢的位置不能放置积木。
其中,小明不喜欢的位置都被标在了图纸上。图纸共有n行,从下至上的每一行分别对应积木的第1层至第n层。每一行都有m个字符,字符可能是‘.’或‘X’,其中‘X’表示这个位置是小明不喜欢的。
现在,小明想要知道,共有多少种放置积木的方案。他找到了参加蓝桥杯的你来帮他计算这个答案。由于这个答案可能很大,你只需要回答这个答案对1000000007(十亿零七)取模后的结果。注意:地基上什么都不放,也算作是方案之一种。
【输入格式】
输入数据的第一行有两个正整数n和m,表示图纸的大小。
随后n行,每行有m个字符,用来描述图纸。每个字符只可能是‘.’或‘X’。
【输出格式】
输出一个整数,表示答案对1000000007取模后的结果。
【样例输入1】
2 3
..X
.X.
【样例输出1】4
【样例说明1】
成功的摆放有(其中O表示放置积木):
(1)
..X
.X.
(2)
..X
OX.
(3)
O.X
OX.
(4)
..X
.XO
【样例输入2】
3 3
..X
.X.
...
【样例输出2】16
【数据规模约定】
对于10%的数据,n=1,m<=30;
对于40%的数据,n<=10,m<=30;
对于100%的数据,n<=100,m<=100。

这题限制条件多,比较复杂。参考答案如下:

[参考答案] (https://blog.dotcpp.com/a/73287)

#include <cstdio>
#include <algorithm>
#define consteval const
#include <cstring>
#define MOD 1000000007
const int MAXN=101;
typedef long long llong;
using namespace std;
char s[MAXN][MAXN+1];
int maxh[MAXN];
int dp[MAXN][MAXN][MAXN][2];
//pre:上一列多少行
int dfs(int left,int right,int pre,bool descend=0) {
	if(dp[left][right][pre][(int)descend]>=0)	   return dp[left][right][pre][(int)descend];
	if(right<left)    return 1;
	int res=0;
	//高度限制。如果已出现下降,那么本列高度就必须不能大于前一列(因为同一行上必须连续摆放)
	int hLim=(descend)?min(pre,maxh[left]):maxh[left];
	//若已出现下降,则后面都必须下降
	for(int i=1; i<=hLim; i++) {
		res=(res+dfs(left+1,right,i,descend||(i<pre)))%MOD;
	}
	return dp[left][right][pre][(int)descend]=res;
}

int main() {
	int n,m;
	int res=1;
	scanf("%hd%hd",&n,&m);
	for(int i=1; i<=n; i++)    scanf("%s",s[i]+1);
	memset(dp,-1,sizeof(dp));
	//计算每列最大高度。由于每一列都要连续摆放,因此第i列大于maxh[i]的行都不能放
	for(int i=1; i<=m; i++) {
		for(int j=n; j>=0; j--) {
			if(s[j][i]!='.') {
				maxh[i]=n-j;
				break;
			}
		}
	}
	int l=1,r=l;
	while(l<=m){
		while(l<=m&&maxh[l]==0)    l++; //为防止连续多个X的情况,这句必须加
		r=l;
		while(r<=m&&maxh[r]>0)    r++;
		r--;
		for(int i=l; i<=r; i++) {
			for(int j=i; j<=r; j++) {
				res=(res+dfs(i,j,1))%MOD;
			}
		}
		l=r+1;
	}
	printf("%d",res);
	return 0;
}
  1. 矩阵求和(15分)

经过重重笔试面试的考验,小明成功进入Macro hard公司工作。今天小明的任务是填满这么一张表:
表有n行n列,行和列的编号都从1算起。其中第i行第j个元素的值是gcd(i, j)的平方,gcd表示最大公约数,以下是这个表的前四行前四列:
1 1 1 1
1 4 1 4
1 1 9 1
1 4 1 16
小明突然冒出一个奇怪的想法,他想知道这张表中所有元素的和。由于表过于庞大,他希望借助计算机的力量。
【输入格式】一行一个正整数 n,意义见题。
【输出格式】一行一个数,表示所有元素的和。由于答案比较大,请输出模 (10^9 + 7)(即:十亿零七) 后的结果。
【样例输入】4
【样例输出】48
「数据范围」
对于30%的数据,n<=1000
存在10%的数据,n=10^5
对于60%的数据,n<=10^6
对于100%的数据,n<=10^7

矩阵肯定是对称的,因此算左下角即可。最后统计答案时只要乘2并加上对角线上的结果即可。复杂度约为O(n2logn)

for(int i=2; i<=n; i++) {
    for(int j=1; j<i; j++) {
        int now=gcd(i,j);
        res=(res+now*now))%MOD;
    }
}
res=(res*2)%MOD;
for(int i=1; i<=n; i++)    res=(res+(llong)i*i)%MOD;

可以先预处理所有素数,i为素数就不进行内层循环,j为素数且i不是j的整数倍就不进行gcd,因为这两种情况的gcd值都为1。不过还是只能过40%

initPrime(n);
for(int i=2; i<=n; i++) {
	res=(res+1)%MOD; //j=1
	if(!np[i]) {
		res=(res+i-2)%MOD;
		continue;
	}
	for(int j=2; j<i; j++) {
		llong tmp=(!np[j]&&i%j!=0)?1:gcd(i,j);
		res=(res+tmp*tmp)%MOD;
	}
}
posted @   m0_51303687  阅读(62)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示