1.练习项目 :
问题描述
地球上,我们习惯用十进制数字来记录万物,从个位、十位,逐级向上构成了我们熟悉的自然数体系。
然而,在遥远的蓝桥星,数字的排列和解读方式却与我们截然不同。蓝桥星人并不单纯地以数值大小来衡量一个数字,他们更注重数字内部蕴含的 "节奏感"。因此,对他们而言,任何一个有效的数字,其从左到右每一位上的数字奇偶性都必须是交替出现的。
例如,对于 10 这个数字,其十位是奇数 1,个位是偶数 0,呈现奇偶交替,因此 10 是个有效的数字。而对于 13 这个数字,其十位是奇数 1,个位也是奇数 3,不符合奇偶交替的条件,因此 13 不是个有效的数字。
根据这个规则,蓝桥星的数字序列从 10 开始,依次为 10,12,14,16,18,21,23,25,27,29,30,...。
只不过,随着文明的发展,蓝桥星人需要一种方法,来快速找到第 N 个符合这种奇偶交替规则的数字,以满足其日益增长的数字处理需求。现在,请你帮助蓝桥星人,编写程序找出并输出第 N 个符合奇偶交替规则的数字。
输入格式
输入包含一个正整数 N,表示需要查找第 N 个符合规则的数字。
输出格式
输出一个整数,表示第 N 个符合奇偶交替规则的数字。
2.选择课程
在蓝桥云课中选择题库,选择题号21258并开始练习。
3.开始练习
(1)源码 :
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
ll a=9*5,b=2,n;
cin>>n;
while(n>a){
n-=a;
b++;
a*=5;
}
n--;
a/=9;
bool las=(n/a+1)%2;
cout<<n/a+1;
n%=a;
for(int i=1;i<b;i++){
a/=5;
cout<<n/a*2+1-las;
las=!las;
n%=a;
}
return 0;
}
(2)检验结果
对此代码进行检验,检验后无报错,提交此代码,判题结果为正确100分。
(3)练习心得:
一、问题模型化
我们只考虑至少两位数的正整数。对于一个 k 位数,从左到右每一位记为 d1d2...dk,其中 d1 是最高位。
规则:相邻两位的奇偶性不同,即:
-
若 di 是奇数,则 di+1 必须是偶数;
-
若 di 是偶数,则 di+1 必须是奇数。
计数:
-
最高位 d1 不能是 0,因此它有 9 种选择:1~9。
-
对于之后的每一位 di(i≥2),它必须与 di−1 奇偶性相反。
-
假设 di−1 是奇数,则 di 必须是偶数,可选 0,2,4,6,8,共 5 种。
-
假设 di−1 是偶数,则 di 必须是奇数,可选 1,3,5,7,9,也是 5 种。
-
-
因此,不管前一位的奇偶性如何,下一位始终有 5 种选择。
-
所以一个 k 位数的合法个数为:
9×5k−1
(最高位 9 种,后面 k−1 位各 5 种)
例子:
-
2 位数:9×51=45 个
-
3 位数:9×52=225 个
-
4 位数:9×53=1125 个
二、算法步骤概览
我们要找第 N 个符合条件的数,算法分两步:
-
确定这个数是几位数
逐步减去 2 位数、3 位数......的个数,直到剩余的数量不超过当前位数的总数。
-
逐位求出数字本身
利用"分组"的思想,将目标数字在当前位置的取值范围内进行索引定位,通过除法确定它属于第几组,再根据奇偶约束转换为实际数字。
代码就精确实现了这个两步策略。
三、逐行代码深度解析
第一步:初始化变量,读入 N
cpp
long long a = 45, b = 2, n;
cin >> n;
-
a = 45表示当前考虑的位数(即b位数)的总个数 。初始b=2代表两位数,两位数的总数是 45,所以a初始化为 45。 -
b是当前位数。 -
n存放输入,表示我们要找第n个(从 1 开始计数)。
第二步:确定最终答案的位数
cpp
while (n > a) {
n -= a;
b++;
a *= 5;
}
这个循环的含义是:
-
如果
n比当前位数的总数a还要大,说明目标数字不在b位数里,应该跳过所有b位数。 -
执行
n -= a:从n中减去b位数的所有数量,这样n就变成了更高位数序列中的第几个。 -
b++:位数加 1。 -
a *= 5:更新新位数的总数。因为从 kk 位变成 k+1k+1 位,总数乘 5(多了一位,多一个 5 选择)。
循环结束后的状态:
-
b就是答案的位数。 -
n表示在b位数中的第n个(从 1 开始计)。 -
a此时是b位数的总个数,即9 * 5^(b-1)。
第三步:准备"逐位构造"
cpp
n--;
a /= 9;
-
n--:将n转换为 0‑based 索引 ,取值范围变成0到a-1。为什么要这样做?因为接下来我们要用除法分组,0‑based 更方便:
例如
n / group_size会直接给出组号(0,1,2,...),这正是我们想要的偏移量。 -
a /= 9:此时a = 9 * 5^(b-1),除以 9 后a = 5^(b-1)。这个
a的意义变了:现在它代表去掉最高位后,剩下b-1位能构成的所有组合数量 。最高位有 9 种选择,剩下位有
5^(b-1)种组合,所以总组合9 * a。当前偏移n(0‑based)可以写成:n=i×a+rn=i×a+r
其中
i = n / a(取值范围 0~8)对应最高位的 9 种选择中的第几个,r = n % a是去掉最高位后剩余部分的偏移量。
第四步:求出最高位
cpp
bool las = (n / a + 1) % 2;
cout << n / a + 1;
n %= a;
-
确定最高位数字
n / a是索引i(0~8),那么最高位数字就是i + 1(因为最高位从 1 到 9)。cout << n / a + 1;直接输出这个数字。 -
记录最高位的奇偶性
(n / a + 1) % 2:-
如果数字是偶数,
% 2为 0; -
如果是奇数,
% 2为 1。把它存到
las变量中,las 代表"上一位的奇偶性"(1 表示奇数,0 表示偶数),这个信息将用于决定下一位应该填奇数还是偶数。
-
-
更新偏移量
nn %= a;此时n变成r,即剩余b-1位中的偏移量(0‑based)。后续我们将在剩余位上重复类似的过程。
第五步:逐位构造剩余的 b-1 位
cpp
for (int i = 1; i < b; i++) {
a /= 5;
cout << n / a * 2 + 1 - las;
las = !las;
n %= a;
}
这个循环运行 b-1 次,每次输出一位数字。
循环内部详解:
-
a /= 5;此时
a代表"当前位之后剩余位数能构成的所有组合数量"。进入循环时,
a是5^(b-1)。第一次循环a /= 5后a = 5^(b-2),第二次5^(b-3),以此类推,直到最后a=1(剩下 0 位,只有 1 种可能,即没有选择了)。 -
n / a当前位有 5 种可能的选择(因为必须与上一位奇偶性相反)。
n / a就是当前位在它的 5 种可能取值中的索引 (0~4)。n是当前剩余的偏移量(0‑based),a是剩余组合数,所以能确定当前位是哪个选项。 -
cout << n / a * 2 + 1 - las;这是将索引
j = n/a转换成实际数字的公式。解释:
-
如果上一位
las是 1(奇数),则本位必须填偶数,可选 {0,2,4,6,8}。这些偶数可以表示为
2 * j,其中j = 0..4分别对应 0,2,4,6,8。 -
如果上一位
las是 0(偶数),则本位必须填奇数,可选 {1,3,5,7,9}。这些奇数可以表示为
2 * j + 1,j = 0..4对应 1,3,5,7,9。统一成一条公式:
数字={2j+1若上一位是偶数 (las=0)2j若上一位是奇数 (las=1)数字={2j+12j若上一位是偶数 (las=0)若上一位是奇数 (las=1)
合并为
2j + 1 - las:-
当
las=0→2j+1(奇数) -
当
las=1→2j(偶数)完美对应。
-
-
las = !las;翻转奇偶性,因为下一位又需要与前一位相反。
!las将 0 变 1,1 变 0。 -
n %= a;更新
n为当前位选定后,剩余低位部分的新偏移量。
四、完整示例追踪:N = 10
目标:找到序列中第 10 个数。已知序列前 10 个为:
10(1), 12(2), 14(3), 16(4), 18(5), 21(6), 23(7), 25(8), 27(9), 29(10)。
我们跟着代码走:
-
初始
a=45, b=2, n=10。 -
while (n > a)→10 > 45否,跳过。所以答案仍为 2 位数,n=10。 -
n--→n=9;a /= 9→a = 5(即 52−1=552−1=5)。 -
最高位:
-
i = n / a = 9 / 5 = 1 -
最高位数字 =
1 + 1 = 2,输出2。 -
las = (2) % 2 = 0(偶数)。 -
n %= a→n = 9 % 5 = 4。
-
-
循环
i=1(因为b=2,循环只一次):-
a /= 5→a = 1。 -
j = n / a = 4 / 1 = 4 -
下一位数字 =
4 * 2 + 1 - las=8 + 1 - 0 = 9,输出9。 -
las = !0 = 1(无后续影响)。 -
n %= a→n = 0。
-
-
最终结果
29,与预期一致。
注意每段代码末尾的分号是否存在 ,如不存在则需即使补充;输入法 是否切换 为英语模式;语法是否错误。