2020年蓝桥杯模拟赛解题报告(CPP版本)
Dev-C++ 常用快捷键大全
第二题:约数个数
题目
【问题描述】
1200000有多少个约数(只计算正约数)。
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
method1:求数 n n n的约数个数:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
scanf("%d", &n);
int r = (int)sqrt(1.0 * n);
int sum = 0;
if(r * r == n)
{
sum++;
r--;
}
for(int i = 1; i <= r; i++)
if(n % i == 0)
{
sum += 2;
}
printf("%d\n", sum);
return 0;
}
method2:枚举
#include <iostream>
using namespace std;
const int N = 1200000;
int main() {
int ans = 0;
for (int i = 1; i <= N; ++i) {
if (N % i == 0)
ans++;
}
cout << ans << endl;
return 0;
}
第三题 叶节点数
题目
【问题描述】
一棵包含有2019个结点的二叉树,最多包含多少个叶结点?
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
思路
n=n0+n1+n2,为使叶子节点数(n0)最多,必须n1最小,设为0,而n0=n2+1
得n2=(2019-1)/2=1009
所以n0=1010
答案
1010
小结:
数字9
【问题描述】
在1至2019中,有多少个数的数位中包含数字9?
注意,有的数中的数位中包含多个9,这个数只算一次。例如,1999这个数包含数字9,在计算时只是算一个数。
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int sum=0;
for(int i=1;i<=2019;i++)
{
int t=i;
while(t>=1)
{
if(t%10==9){
sum++;
break;
}
t=t/10;
}
}
cout<<sum<<'\n';
return 0;
}
数位递增的数
题目
【问题描述】
一个正整数如果任何一个数位不大于右边相邻的数位,则称为一个数位递增的数,例如1135是一个数位递增的数,而1024不是一个数位递增的数。
给定正整数 n,请问在整数 1 至 n 中有多少个数位递增的数?
【输入格式】
输入的第一行包含一个整数 n。
【输出格式】
输出一行包含一个整数,表示答案。
【样例输入】
30
【样例输出】
26
【评测用例规模与约定】
对于 40% 的评测用例,1 <= n <= 1000。
对于 80% 的评测用例,1 <= n <= 100000。
对于所有评测用例,1 <= n <= 1000000。
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,i,j;
cin>>n;
int sum=0;
char s[7];
for(i=1;i<=n;i++){
sprintf(s,"%d",i);
for(j=1;j<strlen(s);j++){
if(s[j]<s[j-1]){
break;
}
}
if(j==strlen(s)){
sum++;
}
}
cout<<sum<<'\n';
return 0;
}
个人小结
长草 (模板题)
题目
【问题描述】
小明有一块空地,他将这块空地划分为 n 行 m 列的小块,每行和每列的长度都为 1。
小明选了其中的一些小块空地,种上了草,其他小块仍然保持是空地。
这些草长得很快,每个月,草都会向外长出一些,如果一个小块种了草,则它将向自己的上、下、左、右四小块空地扩展,这四小块空地都将变为有草的小块。
请告诉小明,k 个月后空地上哪些地方有草。
【输入格式】
输入的第一行包含两个整数 n, m。
接下来 n 行,每行包含 m 个字母,表示初始的空地状态,字母之间没有空格。如果为小数点,表示为空地,如果字母为 g,表示种了草。
接下来包含一个整数 k。
【输出格式】
输出 n 行,每行包含 m 个字母,表示 k 个月后空地的状态。如果为小数点,表示为空地,如果字母为 g,表示长了草。
【样例输入】
4 5
.g…
…
…g…
…
2
【样例输出】
gggg.
gggg.
ggggg
.ggg.
【评测用例规模与约定】
对于 30% 的评测用例,2 <= n, m <= 20。
对于 70% 的评测用例,2 <= n, m <= 100。
对于所有评测用例,2 <= n, m <= 1000,1 <= k <= 1000。
Method2:队中存放已经有草的小块
#include<bits/stdc++.h>
using namespace std;
int dirx[4]={-1,1,0,0};
int diry[4]={0,0,-1,1};
struct block{
int x;
int y;
int State;// 0 已计算过 1 未计算
};
int Map[1001][1001];
int main(){
queue <block> q;//队中存放已经有草的地方
int n,m;
int i,j;
memset(Map,0,sizeof(Map));
cin>>n>>m;
char g;
for(i=1;i<=n;i++){ //输入
for(j=1;j<=m;j++){
cin>>g;
if(g=='g'){
block t;
t.x=i;
t.y=j;
t.State=1;
Map[i][j]=1;
q.push(t);
}
}
}
int k,len;
cin>>k;
while(k--){
len=q.size();
for(i=1;i<=len;i++){
block t = q.front();//取队首
if(t.State==1){
for(int u=0;u<=3;u++){
if(t.x+dirx[u]>=1&&t.x+dirx[u]<=n&&t.y+diry[u]>=1&&t.y+diry[u]<=m){// 上下左右方向在范围内
if(Map[t.x+dirx[u]][t.y+diry[u]]==0){
block New;
Map[t.x+dirx[u]][t.y+diry[u]]=1;
New.x=t.x+dirx[u];
New.y=t.y+diry[u];
New.State=1;
q.push(New);
}
}
}
}
q.pop();//将队首出队
t.State=0;
q.push(t);//原先的队首入队尾
}
}
for(i=1;i<=n;i++){
for(j=1;j<=m;j++){
if(Map[i][j]==1){
cout<<'g';
}
else{
cout<<'.';
}
}
cout<<endl;
}
return 0;
}
Method2(荐):队中仅存放可继续扩展的有草小块
#include<bits/stdc++.h>
using namespace std;
int dirx[4]={-1,1,0,0};
int diry[4]={0,0,-1,1};
struct block{
int x;
int y;
};
int Map[1001][1001];//Map记录已有草的位置
int main(){
queue <block> q;//队中仅存放可继续扩展的有草小块
int n,m;
int i,j;
memset(Map,0,sizeof(Map));
cin>>n>>m;
char g;
for(i=1;i<=n;i++){ //输入
for(j=1;j<=m;j++){
cin>>g;
if(g=='g'){
block t;
t.x=i;
t.y=j;
Map[i][j]=1;
q.push(t);
}
}
}
int k,len;
cin>>k;
while(k--){
len=q.size();
for(i=1;i<=len;i++){
block t = q.front();//取队首
for(int u=0;u<=3;u++){
if(t.x+dirx[u]>=1&&t.x+dirx[u]<=n&&t.y+diry[u]>=1&&t.y+diry[u]<=m){// 上下左右方向在范围内
if(Map[t.x+dirx[u]][t.y+diry[u]]==0){
block New;
Map[t.x+dirx[u]][t.y+diry[u]]=1;
New.x=t.x+dirx[u];
New.y=t.y+diry[u];
q.push(New);
}
}
}
q.pop();//将队首出队 队首所示有草小块四周已拓展完毕
}
}
for(i=1;i<=n;i++){
for(j=1;j<=m;j++){
if(Map[i][j]==1){
cout<<'g';
}
else{
cout<<'.';
}
}
cout<<endl;
}
return 0;
}
小结
bfs,广度优先搜索,用队列暂存当前有草的所有位置,每过一个月,将先有当前队列中已有元素依次出栈,将新长出来的草块入栈,用矩阵Map记录草块的当前情况。
此法可保证每块草地至多被计算一次,时间复杂度为
O
(
N
∗
M
)
O(N*M)
O(N∗M)。
序列计数
小明想知道,满足以下条件的正整数序列的数量:
- 第一项为 n;
- 第二项不超过 n;
- 从第三项开始,每一项小于前两项的差的绝对值。
请计算,对于给定的 n,有多少种满足条件的序列。
【输入格式】
输入一行包含一个整数 n。
【输出格式】
输出一个整数,表示答案。答案可能很大,请输出答案除以10000的余数。
【样例输入】
4
【样例输出】
7
【样例说明】
以下是满足条件的序列:
4 1
4 1 1
4 1 2
4 2
4 2 1
4 3
4 4
【评测用例规模与约定】
对于 20% 的评测用例,1 <= n <= 5;
对于 50% 的评测用例,1 <= n <= 10;
对于 80% 的评测用例,1 <= n <= 100;
对于所有评测用例,1 <= n <= 1000。
思路
由本题第三点的递归定义,可以得到递归公式:
f
(
p
r
e
,
c
u
r
)
=
f
(
c
u
r
,
1
)
+
f
(
c
u
r
,
2
)
+
.
.
.
+
f
(
c
u
r
,
a
b
s
(
p
r
e
−
c
u
r
)
+
1
=
∑
i
=
1
∣
p
r
e
−
c
u
r
∣
f
(
c
u
r
,
i
)
+
1
f(pre,cur) = f(cur,1) + f(cur,2) + ...+f(cur,abs(pre-cur)+1 =\sum_{i=1}^{|pre-cur|}f(cur,i) +1
f(pre,cur)=f(cur,1)+f(cur,2)+...+f(cur,abs(pre−cur)+1=i=1∑∣pre−cur∣f(cur,i)+1
(其中
p
r
e
pre
pre表示前一个数,
c
u
r
cur
cur表示当前数。)
时间复杂度:
O
(
n
3
)
O(n^3)
O(n3)
1.直接递归(暴力,毫无疑问会超时)
#include<bits/stdc++.h>
using namespace std;
int sum=0;
void r(int Abs,int now){
sum++;
sum=sum%10000;
for(int i=1;i<Abs;i++){
if(abs(now-i)>=2){
r(abs(now-i),i);
}
else{
sum++;
sum=sum%10000;
}
}
}
int main()
{
int n;
cin>>n;
for(int j=1;j<=n;j++)
{
r(n-j,j);
}
cout<<sum<<'\n';
return 0;
}
Or
#include<bits/stdc++.h>
using namespace std;
int sum=0;
void r(int Abs,int now){
sum++;
sum=sum%10000;
for(int i=1;i<Abs;i++){
if(abs(now-i)>=2){
r(abs(now-i),i);
}
else{
sum++;
sum=sum%10000;
}
}
}
int main()
{
int n;
cin>>n;
for(int j=1;j<=n;j++)
{
r(n-j,j);
}
cout<<sum<<'\n';
return 0;
}
2.优化,将已计算结果暂存
空间换时间,还是会超时,但已可通过大部分样例。
当 n= 1000时,已可在有限时间内计算出结果。
#include<bits/stdc++.h>
using namespace std;
int sum=0;
int mem[1010][1010];
int r(int pre,int now){
int num = 1;
if(mem[pre][now]!=0){
return mem[pre][now];
}
for(int i=1;i<abs(pre-now);i++){
num += r(now,i)%10000;
}
mem[pre][now] = num;
return num;
}
int main()
{
int n;
memset(mem,0,sizeof(mem));
cin>>n;
for(int j=1;j<=n;j++)
{
sum = (sum+r(n,j))%10000;
}
cout<<sum<<'\n';
return 0;
}
3.进一步优化,减少循环次数(AC)
在上一种情况下, 解的空间是
n
2
n^2
n2,但因为展开循环累加,实际的复杂度还是
n
3
n^3
n3。若可以规避循环累加,则可以将复杂度优化到
n
2
n^2
n2。
重新考虑状态转移,用
f
(
i
,
j
)
f(i,j)
f(i,j)表示
p
r
e
=
i
pre =i
pre=i,当前数是
1
∼
j
1\sim j
1∼j时候的合法序列个数。有:
f
(
i
,
j
)
=
f
(
i
,
j
−
1
)
+
f
(
j
,
a
b
s
(
i
−
j
)
−
1
)
+
1
f(i,j)=f(i,j-1)+f(j,abs(i-j)-1)+1
f(i,j)=f(i,j−1)+f(j,abs(i−j)−1)+1
这样每次解答树只需要展开两个节点,相当于少一次循环,尽管解答树的层次还是很深,但是有记忆存储的辅助,解空间依然是
n
2
n^2
n2。可以在更短的时间内解决问题。
#include<bits/stdc++.h>
#define mod 10000
using namespace std;
int sum=0;
int mem[1010][1010];
int r(int now,int sub){
if(sub<=0){
return 0;
}
int num = 0;
if(mem[now][sub]!=0){
return mem[now][sub];
}
int res = r(now,sub-1)%mod+r(sub,abs(now-sub)-1)%mod+1;
mem[now][sub] = res;
return res;
}
int main()
{
int n;
memset(mem,0,sizeof(mem));
cin>>n;
cout<<r(n,n)<<'\n';
return 0;
}
经验
如何判断自己的算法是否超时??
代码敲完后,如果可以,最大样例输入是一样,看能不能在有限的时间内出结果。一般不超时的算法,几秒钟内就可以出结果。