猜测序列
小明有一个由1到n的整数组成的排列,他让你来猜出这个排列是什么。你每次可以猜测某一位置的数字,小明会告诉你所猜测的数是“大了”、“小了”或是“正确”。你想知道你在最坏情况下,需要猜测几次,才能在排列的所有位置都得到小明“正确”的回复?
输入
5
输出
11
说明
用二分法,对于一个长度为 k 的序列,最多搜索
⌊
log
2
k
⌋
+
1
\lfloor \log_2k\rfloor + 1
⌊log2k⌋+1 次,。对于本例,第一个位置有 5 个,那就是 3 次搜索,第二个有 4 个,3 次……
一共 3 + 3 + 2 + 2 + 1 = 11 次。
那我们一个一个计算,然后加上,多简单,复杂度 O ( n ) O(n) O(n)。
然后呢,超时了,寄。
分析
对于每一个 k :
-
k = 1 ∈ [ 2 0 , 2 1 ) k = 1\in[2^0,\ 2^1) k=1∈[20, 21) == > 搜索 0 + 1 = 1 0 + 1 = 1 0+1=1 次
共 2 0 ∗ ( 0 + 1 ) = 1 2^0 * (0 + 1) = 1 20∗(0+1)=1 次
-
k = 2 ∈ [ 2 1 , 2 2 ) k = 2\in[2^1,\ 2^2) k=2∈[21, 22) == > 搜索 1 + 1 = 2 1 + 1 = 2 1+1=2 次
k = 3 ∈ [ 2 1 , 2 2 ) k = 3\in[2^1,\ 2^2) k=3∈[21, 22) == > 搜索 1 + 1 = 2 1 + 1 = 2 1+1=2 次
共 2 1 ∗ ( 1 + 1 ) = 4 2^1 * (1 + 1) = 4 21∗(1+1)=4次
-
k = 4 ∈ [ 2 2 , 2 3 ) k = 4\in[2^2,\ 2^3) k=4∈[22, 23) == > 搜索 2 + 1 = 3 2 + 1 = 3 2+1=3 次
k = 5 ∈ [ 2 2 , 2 3 ) k = 5\in[2^2,\ 2^3) k=5∈[22, 23) == > 搜索 2 + 1 = 3 2 + 1 = 3 2+1=3 次
k = 6 ∈ [ 2 2 , 2 3 ) k = 6\in[2^2,\ 2^3) k=6∈[22, 23) == > 搜索 2 + 1 = 3 2 + 1 = 3 2+1=3 次
k = 7 ∈ [ 2 2 , 2 3 ) k = 7\in[2^2,\ 2^3) k=7∈[22, 23) == > 搜索 2 + 1 = 3 2 + 1 = 3 2+1=3 次
共 2 2 ∗ ( 2 + 1 ) = 12 2^2 * (2 + 1) = 12 22∗(2+1)=12次 -
k = 8 ……
其实就是个二叉树,求各个节点层数的总和,对于 k ∈ [ 2 i − 1 , 2 i ) k\in [2^{i-1}, \ \ 2^i) k∈[2i−1, 2i) ,有 2 i − 1 2^{i-1} 2i−1个和它一样的(包括它自身)都需要搜索 i i i 次,可以避免多次计算。
图示:
步骤:
- 令
p
=
⌊
log
2
n
⌋
p = \lfloor \log_2n\rfloor
p=⌊log2n⌋,然后从
i = 0
遍历到p - 1
,每次加上 - 最后加上 ( n − 2 p + 1 ) ∗ ( p + 1 ) (n - 2^p + 1) * (p + 1) (n−2p+1)∗(p+1)。
复杂度:
O
(
log
2
n
)
O(\log_2n)
O(log2n)
代码
#include<iostream>
#include<vector>
#include <cmath>
using namespace std;
int main() {
int n;
cin >> n;
unsigned long long ans = 0;
unsigned long long p = floor(log2(n));
for(int i = 0; i < p; i++) {
ans += (i + 1) * pow(2, i);
}
ans += (p + 1) * (n + 1 - pow(2, p));
cout << ans;
}