Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <h1>Latest answer (O(log n) complexity)</h1> <p>If we believe the conjecture by templatetypedef and Aleksi Torhamo (<strong>update</strong>: proof at the end of this post), there is a closed form solution <code>count(n)</code> calculable in <code>O(log n)</code> (or <code>O(1)</code> if we assume logarithm and bit shifting is <code>O(1)</code>):</p> <p>Python:</p> <pre class="lang-python prettyprint-override"><code>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 &lt;&lt; int(log(n,2)) if n &gt; 0 else 0 </code></pre> <p>C++:</p> <pre class="lang-cpp prettyprint-override"><code>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&lt;&lt;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&gt;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; } </code></pre> <p>This code can calculate the result for <code>n=100,000,000</code> (and even <code>n=1e24</code> in Python!) correctly in no time<sup>1</sup>. </p> <p>I have tested the codes with various values for <code>n</code> (using my <code>O(n)</code> solution as the standard, see <strong>Old Answer</strong> section below), and they still seem correct.</p> <p>This code relies on the two conjectures by templatetypedef and Aleksi Torhamo<sup>2</sup>. <s>Anyone wants to proof those? =D</s> (Update 2: PROVEN)</p> <p><sup>1</sup><sub>By no time, I meant almost instantly</sub><br> <sup>2</sup><sub>The conjecture by Aleksi Torhamo on <code>f</code> function has been empirically proven for <code>n&lt;=100,000,000</code></sub></p> <hr /> <h1>Old answer (O(n) complexity)</h1> <p>I can return the count of <code>n=1,000,000</code> (the result is <code>475712</code>) in 1.358s (in my iMac) using Python 2.7. <em>Update</em>: It's 0.198s for <code>n=10,000,000</code> in C++. =)</p> <p>Here is my idea, which achieves <code>O(n)</code> time complexity.</p> <h1>The Algorithm</h1> <h2>Definition of <code>f(n)</code></h2> <p>Define <code>f(n)</code> as the number of bits that will be set on bitvector of length <code>n</code>, <em>assuming that the first and last bit are set</em> (except for <code>n=2</code>, where only the first or last bit is set). So we know some values of <code>f(n)</code> as follows:</p> <pre><code>f(1) = 1 f(2) = 1 f(3) = 2 f(4) = 2 f(5) = 3 </code></pre> <p>Note that this is different from the value that we are looking for, since the initial bit might not be at the first or last, as calculated by <code>f(n)</code>. For example, we have <code>f(7)=3</code> instead of 4.</p> <p>Note that this can be calculated rather efficiently (amortized <code>O(n)</code> to calculate all values of <code>f</code> up to <code>n</code>) using the recurrence relation:</p> <pre><code>f(2n) = f(n)+f(n+1)-1 f(2n+1) = 2*f(n+1)-1 </code></pre> <p>for <code>n&gt;=5</code>, since the next bit set following the rule will be the middle bit, except for <code>n=1,2,3,4</code>. Then we can split the bitvector into two parts, each independent of each other, and so we can calculate the number of bits set using <code>f( floor(n/2) ) + f( ceil(n/2) ) - 1</code>, as illustrated below:</p> <pre> n=11 n=13 10000100001 1000001000001 &lt;----&gt; &lt;-----&gt; f(6)&lt;----&gt; f(7) &lt;-----&gt; f(6) f(7) n=12 n=14 100001000001 10000010000001 &lt;----&gt; &lt;-----&gt; f(6)&lt;-----&gt; f(7) &lt;------&gt; f(7) f(8) </pre> <p>we have the <code>-1</code> in the formula to exclude the double count of the middle bit.</p> <p>Now we are ready to count the solution of original problem.</p> <h2>Definition of <code>g(n,i)</code></h2> <p>Define <code>g(n,i)</code> as the number of bits that will be set on bitvector of length <code>n</code>, following the rules in the problem, where the initial bit is at the <code>i</code>-th bit (1-based). Note that by symmetry the initial bit can be anywhere from the first bit up to the <code>ceil(n/2)</code>-th bit. And for those cases, note that the first bit will be set before any bit in between the first and the initial, and so is the case for the last bit. Therefore the number of bit set in the first partition and the second partition is <code>f(i)</code> and <code>f(n+1-i)</code> respectively.</p> <p>So the value of <code>g(n,i)</code> can be calculated as:</p> <pre><code>g(n,i) = f(i) + f(n+1-i) - 1 </code></pre> <p>following the idea when calculating <code>f(n)</code>.</p> <p>Now, to calculate the final result is trivial.</p> <h2>Definition of <code>g(n)</code></h2> <p>Define <code>g(n)</code> as the count being looked for in the original problem. We can then take the maximum of all possible <code>i</code>, the position of initial bit:</p> <pre> g(n) = max<sub>i=1..ceil(n/2)</sub>(f(i) + f(n+1-i) - 1) </pre> <p><strong>Python code:</strong></p> <pre class="lang-python prettyprint-override"><code>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]&gt;-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 &lt;= n &lt;= 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() </code></pre> <h1>Complexity Analysis</h1> <p>Now, the interesting thing is, what is the complexity of calculating <code>g(n)</code> with the method described above?</p> <p>We should first note that we iterate over <code>n/2</code> values of <code>i</code>, the position of initial bit. And in each iteration we call <code>f(i)</code> and <code>f(n+1-i)</code>. Naive analysis will lead to <code>O(n * O(f(n)))</code>, but actually we used memoization on <code>f</code>, so it's much faster than that, since each value of <code>f(i)</code> is calculated only once, at most. So the complexity is actually added by the time required to calculate all values of <code>f(n)</code>, which would be <code>O(n + f(n))</code> instead.</p> <p>So what's the complexity of initializing <code>f(n)</code>?</p> <p>We can assume that we precompute every value of <code>f(n)</code> first before calculating <code>g(n)</code>. Note that due to the recurrence relation and the memoization, generating the whole values of <code>f(n)</code> takes <code>O(n)</code> time. And the next call to <code>f(n)</code> will take <code>O(1)</code> time.</p> <p>So, the overall complexity is <code>O(n+n) = O(n)</code>, as evidenced by this running time in my iMac for <code>n=1,000,000</code> and <code>n=10,000,000</code>:</p> <pre> &gt; python max_vec_bit.py Enter n (1 &lt;= n &lt;= 10,000,000; 0 to stop): 1000000 g(1000000) = 475712, in 1.358s Enter n (1 &lt;= n &lt;= 10,000,000; 0 to stop): 0 &gt; &gt; &lt;restarted the program to remove the effect of memoization&gt; &gt; &gt; python max_vec_bit.py Enter n (1 &lt;= n &lt;= 10,000,000; 0 to stop): 10000000 g(10000000) = 4757120, in 13.484s Enter n (1 &lt;= n &lt;= 10,000,000; 0 to stop): 6745231 g(6745231) = 3145729, in 3.072s Enter n (1 &lt;= n &lt;= 10,000,000; 0 to stop): 0 </pre> <p>And as a by-product of memoization, the calculation of lesser value of <code>n</code> will be much faster after the first call to large <code>n</code>, as you can also see in the sample run. And with language better suited for number crunching such as C++, you might get significantly faster running time</p> <p>I hope this helps. =)</p> <h2>The code using C++, for performance improvement</h2> <p>The result in C++ is about 68x faster (measured by <code>clock()</code>):</p> <pre> &gt; ./a.out Enter n (1 &lt;= n &lt;= 10,000,000; 0 to stop): 1000000 g(1000000) = 475712, in 0.020s Enter n (1 &lt;= n &lt;= 10,000,000; 0 to stop): 0 &gt; &gt; &lt;restarted the program to remove the effect of memoization&gt; &gt; &gt; ./a.out Enter n (1 &lt;= n &lt;= 10,000,000; 0 to stop): 10000000 g(10000000) = 4757120, in 0.198s Enter n (1 &lt;= n &lt;= 10,000,000; 0 to stop): 6745231 g(6745231) = 3145729, in 0.047s Enter n (1 &lt;= n &lt;= 10,000,000; 0 to stop): 0 </pre> <p>Code in C++:</p> <pre class="lang-cpp prettyprint-override"><code>#include &lt;cstdio&gt; #include &lt;cstring&gt; #include &lt;ctime&gt; int mem_f[10000001]; int f(int n){ if(mem_f[n]&gt;-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&lt;=(n+1)/2; i++){ int cnt = f(i)+f(n+1-i)-1; result = (cnt &gt; 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 &lt;= n &lt;= 10,000,000; 0 to stop): "); scanf("%d",&amp;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); } } </code></pre> <hr /> <h2>Proof</h2> <p><em>note that for the sake of keeping this answer (which is already very long) simple, I've skipped some steps in the proof</em></p> <p><strong>Conjecture of Aleksi Torhamo on the value of <code>f</code></strong></p> <pre> For `n>=1`, prove that: f(2<sup>n</sup>+k) = 2<sup>n-1</sup>+1 for k=1,2,…,2<sup>n-1</sup> ...(1) f(2<sup>n</sup>+k) = k for k=2<sup>n-1</sup>+1,…,2<sup>n</sup> ...(2) given f(0)=f(1)=f(2)=1 </pre> <p>The result above can be easily proven using induction on the recurrence relation, by considering the four cases:</p> <ul> <li>Case 1: (1) for even <code>k</code></li> <li>Case 2: (1) for odd <code>k</code></li> <li>Case 3: (2) for even <code>k</code></li> <li>Case 4: (2) for odd <code>k</code></li> </ul> <pre> Suppose we have the four cases proven for n. Now consider n+1. Case 1: f(2<sup>n+1</sup>+2i) = f(2<sup>n</sup>+i) + f(2<sup>n</sup>+i+1) - 1, for i=1,…,2<sup>n-1</sup> = 2<sup>n-1</sup>+1 + 2<sup>n-1</sup>+1 - 1 = 2<sup>n</sup>+1 Case 2: f(2<sup>n+1</sup>+2i+1) = 2*f(2<sup>n</sup>+i+1) - 1, for i=0,…,2<sup>n-1</sup>-1 = 2*(2<sup>n-1</sup>+1) - 1 = 2<sup>n</sup>+1 Case 3: f(2<sup>n+1</sup>+2i) = f(2<sup>n</sup>+i) + f(2<sup>n</sup>+i+1) - 1, for i=2<sup>n-1</sup>+1,…,2<sup>n</sup> = i + (i+1) - 1 = 2i Case 4: f(2<sup>n+1</sup>+2i+1) = 2*f(2<sup>n</sup>+i+1) - 1, for i=2<sup>n-1</sup>+1,…,2<sup>n</sup>-1 = 2*(i+1) - 1 = 2i+1 </pre> <p>So by induction the conjecture is proven.</p> <p><strong>Conjecture of templatetypedef on the best position</strong></p> <pre> For n>=1 and k=1,…,2<sup>n</sup>, prove that g(2<sup>n</sup>+k) = g(2<sup>n</sup>+k, 2<sup>n</sup>+1) That is, prove that placing the first bit on the 2<sup>n</sup>+1-th position gives maximum number of bits set. </pre> <p>The proof:</p> <pre> First, we have g(2<sup>n</sup>+k,2<sup>n</sup>+1) = f(2<sup>n</sup>+1) + f(k-1) - 1 Next, by the formula of f, we have the following equalities: f(2<sup>n</sup>+1-i) = f(2<sup>n</sup>+1), for i=-2<sup>n-1</sup>,…,-1 f(2<sup>n</sup>+1-i) = f(2<sup>n</sup>+1)-i, for i=1,…,2<sup>n-2</sup>-1 f(2<sup>n</sup>+1-i) = f(2<sup>n</sup>+1)-2<sup>n-2</sup>, for i=2<sup>n-2</sup>,…,2<sup>n-1</sup> and also the following inequality: f(k-1+i) &lt;= f(k-1), for i=-2<sup>n-1</sup>,…,-1 f(k-1+i) &lt;= f(k-1)+i , for i=1,…,2<sup>n-2</sup>-1 f(k-1+i) &lt;= f(k-1)+2<sup>n-2</sup>, for i=2<sup>n-2</sup>,…,2<sup>n-1</sup> and so we have: f(2<sup>n</sup>+1-i)+f(k-1+i) &lt;= f(2<sup>n</sup>+1)+f(k-1), for i=-2<sup>n-1</sup>,…,2<sup>n-1</sup> Now, note that we have: g(2<sup>n</sup>+k) = max<sub>i=1..ceil(2<sup>n-1</sup>+1-k/2)</sub>(f(i) + f(2<sup>n</sup>+k+1-i) - 1) &lt;= f(2<sup>n</sup>+1) + f(k-1) - 1 = g(2<sup>n</sup>+k,2<sup>n</sup>+1) </pre> <p>And so the conjecture is proven.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload