[JZOJ] 01知多少
\(\color{red}{\mathcal{Description}}\)
从 \(1\) 到 \(n\) 有多少个数的十进制表示中只由 \(0\) 和 \(1\) 构成.
\(\color{red}{\mathcal{Input\ Format}}\)
输入一个整数 \(n\).
\(\color{red}{\mathcal{Output\ Format}}\)
输出一个整数.
\(\color{red}{\mathcal{DataSize\ Agreement}}\)
\(1 \leq n \leq 10^9\)
\(\color{red}{\mathcal{Solution}}\)
一道比较巧妙的题目
首先看到就想到暴力,但是看了数据范围绝对不可做,所以先打了个表(但是后来才发现根本不需要)
想了一下,看到 \(0\) 和 \(1\) ,不难想到二进制,但是这怎么和二进制扯上关系呢
这里介绍 \(3\) 种方法
\(\color{blue}{\mathcal{Solution\ }1}\)
因为满足要求的数只由 \(0\) 和 \(1\) 构成,我们可以通过递归计算出每次符合条件的数,直到大于 \(n\) 就停止
\(\color{blue}{\mathcal{Code}}\)
#include <bits/stdc++.h>
#define LL long long
#define reg register
using namespace std;
int N, Ans = 1;
void dfs(int sum) {
if (sum * 10 > N) return ;
Ans++;
dfs(sum * 10);
if (sum * 10 + 1 > N) return ;
Ans++;
dfs(sum * 10 + 1);
}
int main() {
scanf("%d", &N);
dfs(1);
printf("%d\n", Ans);
return 0;
}
\(\color{blue}{\mathcal{Solution\ }2}\)
我们尝试把十进制非零自然数从小到大进行二进制的转换:
\((1)_{10} - (1)_2\)
\((2)_{10} - (10)_2\)
\((3)_{10} - (11)_2\)
\((4)_{10} - (100)_2\)
对照打出的表可以发现,在区间 \([1,n]\) 范围内,十进制 \(i\) 的二进制数恰好是第 \(i\) 个符合条件的数!
得到这样的规律以后就非常简单了
\(\color{blue}{\mathcal{Code}}\)
/*遍历每个十进制数,将该数先转换成二进制的形式,然后再把二进制形式转换成一个01构成的十进制数, 一一对应,超过n就结束
1=>1 2=>10 3==>11 4=>100 5=>101 6=>110 ....
个数 二进制 十进制
1 1 1
2 10 10
3 11 11
4 100 100
5 101 101
6 110 110
7 111 111
8 1000 1000
9 1001 1001
*/
#include <bits/stdc++.h>
#define LL long long
#define reg register
using namespace std;
const int kSize = 10;
int N, num[kSize];
int main() {
scanf("%d", &N);
for (reg int i = 1; ; ++i) {
int s = i, size = 0;
while (s) { num[++size] = s % 2; s >>= 1;}
s = 0;
for (reg int j = size; j >= 1; --j)
s = s * 10 + num[j];
if (s > N) return printf("%d\n", i - 1), 0;
}
return 0;
}
\(\color{blue}{\mathcal{Solution\ }3}\)
数学方法,对于一个整数 \(m\) ,如果它的第 \(i\) 位大于等于 \(1\),那么就有 \(2^{i-1}\) 种方案
具体看代码
\(\color{blue}{\mathcal{Code}}\)
#include <bits/stdc++.h>
#define LL long long
#define reg register
using namespace std;
const int kSize = 10;
int N, Ans = 0, num[kSize];
int Qpow(int n, int k) {
int ret = 1;
for (; k; k >>= 1, n = n * n) if (k & 1) ret *= n;
return ret;
}
int main() {
scanf("%d", &N);
int size = 0;
while (N) { num[++size] = N % 10; N /= 10; }
for (reg int i = size; i >= 1; --i) { //从高位到低位
if (num[i] > 1) return printf("%d\n", Ans + Qpow(2, i) - 1), 0; //数位上不是0就是1, 所以每一位两种情况 2^i个数, 但是0不算,所以减一
else
if (num[i] == 1) Ans += Qpow(2, i - 1);
}
printf("%d\n", Ans);
return 0;
}