最新答案(O(log n) 复杂度)
如果我们相信 templatetypedef 和 Aleksi Torhamo 的猜想(更新:本文末尾的证明),则在 O(log n)(或 @987654323 @如果我们假设对数和位移是O(1)):
Python:
from math import log
def count(n): # The count, using position k conjectured by templatetypedef
k = p(n-1)+1
count_left = k/2
count_right = f(n-k+1)
return count_left + count_right
def f(n): # The f function calculated using Aleksi Torhamo conjecture
return max(p(n-1)/2 + 1, n-p(n-1))
def p(n): # The largest power of 2 not exceeding n
return 1 << int(log(n,2)) if n > 0 else 0
C++:
int log(int n){ // Integer logarithm, by counting the number of leading 0
return 31-__builtin_clz(n);
}
int p(int n){ // The largest power of 2 not exceeding n
if(n==0) return 0;
return 1<<log(n);
}
int f(int n){ // The f function calculated using Aleksi Torhamo conjecture
int val0 = p(n-1);
int val1 = val0/2+1;
int val2 = n-val0;
return val1>val2 ? val1 : val2;
}
int count(int n){ // The count, using position k conjectured by templatetypedef
int k = p(n-1)+1;
int count_left = k/2;
int count_right = f(n-k+1);
return count_left + count_right;
}
此代码可以立即正确计算n=100,000,000(甚至是Python 中的n=1e24!)的结果1。
我已经用 n 的各种值测试了代码(使用我的 O(n) 解决方案作为标准,请参阅下面的旧答案部分),它们似乎仍然正确。
此代码依赖于 templatetypedef 和 Aleksi Torhamo2 的两个猜想。 有人想证明这些吗? =D(更新 2:证明)
1没有时间,我的意思是几乎立即
2Aleksi Torhamo 对f 函数的猜想已针对n<=100,000,000
进行了经验证明
旧答案(O(n) 复杂度)
我可以使用 Python 2.7 在 1.358 秒内(在我的 iMac 中)返回 n=1,000,000 的计数(结果是 475712)。 更新:在 C++ 中 n=10,000,000 是 0.198 秒。 =)
这是我的想法,达到O(n)时间复杂度。
算法
f(n)的定义
将f(n) 定义为将在长度为n 的位向量上设置的位数,假设设置了第一个和最后一个位(n=2 除外,其中只有第一个或最后一个位被设置)。所以我们知道f(n)的一些值如下:
f(1) = 1
f(2) = 1
f(3) = 2
f(4) = 2
f(5) = 3
请注意,这与我们正在寻找的值不同,因为初始位可能不在第一个或最后一个,如f(n) 计算的那样。例如,我们有 f(7)=3 而不是 4。
请注意,这可以使用递归关系相当有效地计算(摊销O(n) 以计算f 到n 的所有值):
f(2n) = f(n)+f(n+1)-1
f(2n+1) = 2*f(n+1)-1
对于n>=5,因为遵循规则设置的下一个位将是中间位,n=1,2,3,4 除外。然后我们可以将位向量分成两部分,每部分相互独立,这样我们就可以使用f( floor(n/2) ) + f( ceil(n/2) ) - 1计算出所设置的位数,如下图所示:
n=11 n=13
10000100001 1000001000001
f(6) f(7)
f(6) f(7)
n=12 n=14
100001000001 10000010000001
f(6) f(7)
f(7) f(8)
我们在公式中有-1 来排除中间位的双重计数。
现在我们准备计算原始问题的解决方案。
g(n,i)的定义
按照问题中的规则,将g(n,i) 定义为将在长度为n 的位向量上设置的位数,其中初始位位于i-th 位(从1 开始)。请注意,通过对称性,初始位可以是从第一位到ceil(n/2)-th 位的任何位置。对于这些情况,请注意第一位将在第一位和初始位之间的任何位之前设置,最后一位也是如此。因此第一个分区和第二个分区设置的位数分别为f(i)和f(n+1-i)。
所以g(n,i)的值可以计算为:
g(n,i) = f(i) + f(n+1-i) - 1
在计算f(n)时遵循这个想法。
现在,计算最终结果很简单。
g(n)的定义
将g(n) 定义为在原始问题中查找的计数。然后我们可以取所有可能的i中的最大值,即初始位的位置:
g(n) = max
i=1..ceil(n/2)(f(i) + f(n+1-i) - 1)
Python 代码:
import time
mem_f = [0,1,1,2,2]
mem_f.extend([-1]*(10**7)) # This will take around 40MB of memory
def f(n):
global mem_f
if mem_f[n]>-1:
return mem_f[n]
if n%2==1:
mem_f[n] = 2*f((n+1)/2)-1
return mem_f[n]
else:
half = n/2
mem_f[n] = f(half)+f(half+1)-1
return mem_f[n]
def g(n):
return max(f(i)+f(n+1-i)-1 for i in range(1,(n+1)/2 + 1))
def main():
while True:
n = input('Enter n (1 <= n <= 10,000,000; 0 to stop): ')
if n==0: break
start_time = time.time()
print 'g(%d) = %d, in %.3fs' % (n, g(n), time.time()-start_time)
if __name__=='__main__':
main()
复杂性分析
现在,有趣的是,用上述方法计算g(n) 的复杂度是多少?
我们首先应该注意,我们迭代了i 的n/2 值,即初始位的位置。在每次迭代中,我们调用f(i) 和f(n+1-i)。天真的分析会导致O(n * O(f(n))),但实际上我们在f上使用了memoization,所以它比这快得多,因为f(i)的每个值最多只计算一次。因此,计算f(n) 的所有值所需的时间实际上增加了复杂性,而不是O(n + f(n))。
那么初始化f(n)的复杂度是多少?
我们可以假设在计算g(n) 之前,我们首先预先计算了f(n) 的每个值。请注意,由于递归关系和记忆,生成f(n) 的整个值需要O(n) 时间。下一次调用f(n) 需要O(1) 时间。
因此,总体复杂度为O(n+n) = O(n),这可以从我的 iMac 中n=1,000,000 和n=10,000,000 的运行时间证明:
> python max_vec_bit.py
输入 n(1
>
>
> python max_vec_bit.py
输入 n(1
作为记忆化的副产品,在第一次调用大 n 后,n 的较小值的计算会快得多,正如您在示例运行中所见。并且使用更适合数字运算的语言(例如 C++),您可能会获得显着更快的运行时间
我希望这会有所帮助。 =)
使用 C++ 的代码,用于提高性能
C++ 中的结果大约快 68 倍(由clock() 测量):
> ./a.out
输入 n(1
>
>
> ./a.out
输入 n(1
C++ 代码:
#include <cstdio>
#include <cstring>
#include <ctime>
int mem_f[10000001];
int f(int n){
if(mem_f[n]>-1)
return mem_f[n];
if(n%2==1){
mem_f[n] = 2*f((n+1)/2)-1;
return mem_f[n];
} else {
int half = n/2;
mem_f[n] = f(half)+f(half+1)-1;
return mem_f[n];
}
}
int g(int n){
int result = 0;
for(int i=1; i<=(n+1)/2; i++){
int cnt = f(i)+f(n+1-i)-1;
result = (cnt > result ? cnt : result);
}
return result;
}
int main(){
memset(mem_f,-1,sizeof(mem_f));
mem_f[0] = 0;
mem_f[1] = mem_f[2] = 1;
mem_f[3] = mem_f[4] = 2;
clock_t start, end;
while(true){
int n;
printf("Enter n (1 <= n <= 10,000,000; 0 to stop): ");
scanf("%d",&n);
if(n==0) break;
start = clock();
int result = g(n);
end = clock();
printf("g(%d) = %d, in %.3fs\n",n,result,((double)(end-start))/CLOCKS_PER_SEC);
}
}
证明
请注意,为了保持这个答案(已经很长)简单,我跳过了证明中的一些步骤
Aleksi Torhamo 关于f值的猜想
对于`n>=1`,证明:
f(2
n+k) = 2
n-1+1 for k=1,2,…,2
n-1 .. .(1)
f(2
n+k) = k for k=2
n-1+1,…,2
n ...(2)
给定 f(0)=f(1)=f(2)=1
上面的结果可以很容易地通过对递归关系的归纳来证明,通过考虑四种情况:
- 案例1:(1)偶数
k
- 案例2:(1)奇数
k
- 案例3:(2)偶数
k
- 案例4:(2)奇数
k
假设我们已经证明了 n 的四种情况。现在考虑n+1。
情况1:
f(2
n+1+2i) = f(2
n+i) + f(2
n+i+1) - 1 , 对于 i=1,…,2
n-1
= 2
n-1+1 + 2
n-1+1 - 1
= 2
n+1
案例二:
f(2
n+1+2i+1) = 2*f(2
n+i+1) - 1,对于 i=0,…,2
n-1-1
= 2*(2
n-1+1) - 1
= 2
n+1
案例3:
f(2
n+1+2i) = f(2
n+i) + f(2
n+i+1) - 1 , 对于 i=2
n-1+1,…,2
n
= i + (i+1) - 1
= 2i
案例4:
f(2
n+1+2i+1) = 2*f(2
n+i+1) - 1,对于 i=2
n-1 +1,…,2
n-1
= 2*(i+1) - 1
= 2i+1
所以通过归纳,猜想得到证明。
templatetypedef关于最佳位置的猜想
对于 n>=1 和 k=1,…,2
n,证明 g(2
n+k) = g(2
n +k, 2
n+1)
也就是说,证明将第一位放在第 2
n+1 个位置会给出最大位数。
证明:
首先,我们有
g(2
n+k,2
n+1) = f(2
n+1) + f(k-1) - 1
接下来,通过 f 的公式,我们有以下等式:
f(2
n+1-i) = f(2
n+1), 对于 i=-2
n-1,…, -1
f(2
n+1-i) = f(2
n+1)-i, 对于 i=1,…,2
n-2支持>-1
f(2n+1-i) = f(2n+1)-2n-2,对于 i=2n-2,…,2n-1
还有以下不等式:
f(k-1+i) n-1,…,-1
f(k-1+i) n-2-1
f(k-1+i) n-2,对于 i=2
n-2,…,2
n-1
所以我们有:
f(2
n+1-i)+f(k-1+i) n+1)+f(k-1),对于i=-2
n-1,…,2
n-1
现在,请注意我们有:
g(2
n+k) = max
i=1..ceil(2n-1+1-k/2)(f (i) + f(2
n+k+1-i) - 1)
n+1) + f(k-1) - 1
= g(2
n+k,2
n+1)
所以这个猜想被证明了。