ผลรวมหลักของสัมประสิทธิ์ทวินามกลาง


13

ภารกิจคือเพื่อดูว่าคุณสามารถคำนวณ n เลือก n / 2 ได้เร็วกว่าฟังก์ชั่นบิวอินในไพ ธ อน แน่นอนสำหรับ n ที่มีขนาดใหญ่นี่คือตัวเลขที่ค่อนข้างใหญ่ดังนั้นแทนที่จะเอาท์พุททั้งจำนวนคุณควรเอาท์พุทผลรวมของตัวเลข ตัวอย่างเช่นสำหรับคำตอบคือn = 100000 135702สำหรับมันเป็นn=10000001354815

นี่คือรหัสหลาม:

from scipy.misc import comb
def sum_digits(n):
   r = 0
   while n:
       r, n = r + n % 10, n / 10
   return r
sum_digits(comb(n,n/2,exact=True))

(highest n on your machine using your code)/(highest n on your machine using my code)คะแนนของคุณคือ รหัสของคุณจะต้องสิ้นสุดใน 60 วินาทีหรือน้อยกว่า

โปรแกรมของคุณจะต้องให้ผลลัพธ์ที่ถูกต้องสำหรับทุกคนแม้กระทั่ง n: 2 <= n <= (สูงสุดของคุณ)

คุณไม่สามารถใช้โค้ด builtin หรือไลบรารีใด ๆ ที่คำนวณค่าสัมประสิทธิ์ทวินามหรือค่าที่สามารถแปลงเป็นค่าสัมประสิทธิ์ทวินามได้อย่างรวดเร็ว

คุณสามารถใช้ภาษาที่คุณเลือกได้


คำตอบ ที่นำคำตอบชั้นนำในปัจจุบันกับ 680.09 ที่น่าตื่นตาตื่นใจคือโดย justhalf


2
เราควรจะส่งวิธีแก้ปัญหาในหลามหรือในภาษาที่เลือก?

เป็นไปได้ที่จะเขียนกิจวัตรที่ทำสิ่งนี้บนคอมพิวเตอร์ที่ทันสมัยและnคำนึงถึงคนนับล้านในขณะที่ฉันสงสัยว่าฟังก์ชั่น Python จะจัดการกับสิ่งที่ยิ่งใหญ่กว่าn = 1e5โดยไม่ทำให้หายใจไม่ออก
COTO

@Alessandro คุณสามารถใช้ภาษาที่คุณเลือกได้ ข้อ จำกัด เพียงอย่างเดียวคือคุณไม่สามารถใช้ฟังก์ชัน builtin เพื่อคำนวณค่าสัมประสิทธิ์

2
อนุญาตให้ใช้ฟังก์ชันแฟกทอเรียลหรือไม่ ฉันสันนิษฐานว่าไม่ใช่เพราะพวกเขาสามารถ "เปลี่ยนให้เป็นสัมประสิทธิ์ทวินามได้อย่างรวดเร็ว" (สิ่งทั้งปวงเป็นเพียงหนึ่งแฟคทอเรียลหารด้วยอีกแฟคทอเรียลกำลังสอง) แต่เนื่องจากคำตอบใช้หนึ่งตอนนี้ความชัดเจนจะดี
Geobits

1
@Comintern: ฉันได้จำลองจุดอ้างอิงนั้นด้วย 287mil ใน 1 นาทีหรือ 169mil ใน 35 วินาที! :)
justhalf

คำตอบ:


9

C ++ (GMP) - (287,000,000 / 422,000) = 680.09

รวมทฤษฎีบทของ Kummer โดย xnor และ GMP เข้าด้วยกันโดย qwr ยังไม่ใกล้เคียงกับโซลูชัน Go ไม่แน่ใจว่าทำไม

แก้ไข:ขอบคุณ Keith Randall สำหรับการเตือนว่าการคูณจะเร็วขึ้นหากจำนวนใกล้เคียงกับขนาด ฉันใช้การคูณหลายระดับคล้ายกับแนวคิดการรวมหน่วยความจำในการจัดการหน่วยความจำ และผลลัพธ์ก็น่าประทับใจ สิ่งที่ใช้ในการ 51s ตอนนี้ใช้เวลาเพียง 0.5 วินาที (เช่นการปรับปรุง 100 เท่า !!)

รหัสเก่า (n = 14,000,000)
เสร็จสิ้นการกรองใน 0.343 วินาที
เสร็จสิ้นการคำนวณ binom ใน 51.929s
เสร็จสิ้นการสรุปใน 0.901 วินาที
14000000: 18954729

จริง 0m53.194s
ผู้ใช้ 0m53.116s
sys 0m0.060s

รหัสใหม่ (n = 14,000,000)
เสร็จสิ้นการกรองใน 0.343 วินาที
เสร็จสิ้นการคำนวณ binom ใน 0.552s
เสร็จสิ้นการสรุปใน 0.902 วินาที
14000000: 18954729

จริง 0m1.804s
ผู้ใช้ 0m1.776s
sys 0m0.023s

การวิ่งเพื่อ n=287,000,000

เสร็จสิ้นการกรองใน 4.211s
เสร็จสิ้นการคำนวณ binom ใน 17.934s
เสร็จสิ้นการรวมใน 37.677s
287000000: 388788354

จริง 0m59.928s
ผู้ใช้ 0m58.759s
sys 0m1.116s

รหัส. รวบรวมกับ-lgmp -lgmpxx -O3

#include <gmpxx.h>
#include <iostream>
#include <time.h>
#include <cstdio>

const int MAX=287000000;
const int PRIME_COUNT=15700000;

int primes[PRIME_COUNT], factors[PRIME_COUNT], count;
bool sieve[MAX];
int max_idx=0;

void run_sieve(){
    sieve[2] = true;
    primes[0] = 2;
    count = 1;
    for(int i=3; i<MAX; i+=2){
        sieve[i] = true;
    }
    for(int i=3; i<17000; i+=2){
        if(!sieve[i]) continue;
        for(int j = i*i; j<MAX; j+=i){
            sieve[j] = false;
        }
    }
    for(int i=3; i<MAX; i+=2){
        if(sieve[i]) primes[count++] = i;
    }
}

mpz_class sum_digits(mpz_class n){
    clock_t t = clock();
    char* str = mpz_get_str(NULL, 10, n.get_mpz_t());
    int result = 0;
    for(int i=0;str[i]>0;i++){
        result+=str[i]-48;
    }
    printf("Done summing in %.3fs\n", ((float)(clock()-t))/CLOCKS_PER_SEC);
    return result;
}

mpz_class nc2_fast(const mpz_class &x){
    clock_t t = clock();
    int prime;
    const unsigned int n = mpz_get_ui(x.get_mpz_t());
    const unsigned int n2 = n/2;
    unsigned int m;
    unsigned int digit;
    unsigned int carry=0;
    unsigned int carries=0;
    mpz_class result = 1;
    mpz_class prime_prods = 1;
    mpz_class tmp;
    mpz_class tmp_prods[32], tmp_prime_prods[32];
    for(int i=0; i<32; i++){
        tmp_prods[i] = (mpz_class)NULL;
        tmp_prime_prods[i] = (mpz_class)NULL;
    }
    for(int i=0; i< count; i++){
        prime = primes[i];
        carry=0;
        carries=0;
        if(prime > n) break;
        if(prime > n2){
            tmp = prime;
            for(int j=0; j<32; j++){
                if(tmp_prime_prods[j] == NULL){
                    tmp_prime_prods[j] = tmp;
                    break;
                } else {
                    mpz_mul(tmp.get_mpz_t(), tmp.get_mpz_t(), tmp_prime_prods[j].get_mpz_t());
                    tmp_prime_prods[j] = (mpz_class)NULL;
                }
            }
            continue;
        }
        m=n2;
        while(m>0){
            digit = m%prime;
            carry = (2*digit + carry >= prime) ? 1 : 0;
            carries += carry;
            m/=prime;
        }
        if(carries>0){
            tmp = 0;
            mpz_ui_pow_ui(tmp.get_mpz_t(), prime, carries);
            for(int j=0; j<32; j++){
                if(tmp_prods[j] == NULL){
                    tmp_prods[j] = tmp;
                    break;
                } else {
                    mpz_mul(tmp.get_mpz_t(), tmp.get_mpz_t(), tmp_prods[j].get_mpz_t());
                    tmp_prods[j] = (mpz_class)NULL;
                }
            }
        }
    }
    result = 1;
    prime_prods = 1;
    for(int j=0; j<32; j++){
        if(tmp_prods[j] != NULL){
            mpz_mul(result.get_mpz_t(), result.get_mpz_t(), tmp_prods[j].get_mpz_t());
        }
        if(tmp_prime_prods[j] != NULL){
            mpz_mul(prime_prods.get_mpz_t(), prime_prods.get_mpz_t(), tmp_prime_prods[j].get_mpz_t());
        }
    }
    mpz_mul(result.get_mpz_t(), result.get_mpz_t(), prime_prods.get_mpz_t());
    printf("Done calculating binom in %.3fs\n", ((float)(clock()-t))/CLOCKS_PER_SEC);
    return result;
}

int main(int argc, char* argv[]){
    const mpz_class n = atoi(argv[1]);
    clock_t t = clock();
    run_sieve();
    printf("Done sieving in %.3fs\n", ((float)(clock()-t))/CLOCKS_PER_SEC);
    std::cout << n << ": " << sum_digits(nc2_fast(n)) << std::endl;
    return 0;
}

2
ตัวคูณจะมีประสิทธิภาพมากกว่าถ้าตัวถูกดำเนินการทั้งสองมีขนาดเท่ากัน คุณมักคูณจำนวนมากด้วยจำนวนน้อย หากคุณรวมตัวเลขขนาดเล็กเป็นคู่ ๆ ซ้ำ ๆ มันอาจจะเร็วกว่า (แต่ต้องใช้หน่วยความจำมากขึ้น)
Keith Randall

ว้าวนั่นสร้างความแตกต่างอย่างมากมาย มันเร็วกว่าการชี้แจง ตอนนี้ฉันสามารถเข้าถึง 169mil ใน 35 วินาที
justhalf

ว้าวแน่นอน! การแบ่งเวลาในส่วนต่าง ๆ ของรหัสของคุณคืออะไร

ฉันได้ตอบคำถามแล้ว 4s ในการสร้างจำนวนเฉพาะถึงn18 วินาทีคำนวณค่าสัมประสิทธิ์ทวินามกลางและ 37 ส่วนที่เหลือในการแปลงผลลัพธ์เป็นสตริงและรวมหลัก
justhalf

1
ฉันรู้สึกว่าคำตอบนี้ควรมีส่วนร่วมกับห้องสมุดโอเพ่นซอร์สใด ๆ ที่คำนวณค่าสัมประสิทธิ์ทวินาม ฉันไม่อยากเชื่อเลยว่าคนอื่นมีรหัสนี้อย่างรวดเร็ว!

7

ไป 33.96 = (16300000/480000)

package main

import "math/big"

const n = 16300000

var (
    sieve     [n + 1]bool
    remaining [n + 1]int
    count     [n + 1]int
)

func main() {
    println("finding primes")
    for p := 2; p <= n; p++ {
        if sieve[p] {
            continue
        }
        for i := p * p; i <= n; i += p {
            sieve[i] = true
        }
    }

    // count net number of times each prime appears in the result.
    println("counting factors")
    for i := 2; i <= n; i++ {
        remaining[i] = i
    }
    for p := 2; p <= n; p++ {
        if sieve[p] {
            continue
        }

        for i := p; i <= n; i += p {
            for remaining[i]%p == 0 { // may have multiple factors of p
                remaining[i] /= p

                // count positive for n!
                count[p]++
                // count negative twice for ((n/2)!)^2
                if i <= n/2 {
                    count[p] -= 2
                }
            }
        }
    }

    // ignore all the trailing zeros
    count[2] -= count[5]
    count[5] = 0

    println("listing factors")
    var m []uint64
    for i := 0; i <= n; i++ {
        for count[i] > 0 {
            m = append(m, uint64(i))
            count[i]--
        }
    }

    println("grouping factors")
    m = group(m)

    println("multiplying")
    x := mul(m)

    println("converting to base 10")
    d := 0
    for _, c := range x.String() {
        d += int(c - '0')
    }
    println("sum of digits:", d)
}

// Return product of elements in a.
func mul(a []uint64) *big.Int {
    if len(a) == 1 {
        x := big.NewInt(0)
        x.SetUint64(a[0])
        return x
    }
    m := len(a) / 2
    x := mul(a[:m])
    y := mul(a[m:])
    x.Mul(x, y) // fast because x and y are about the same length
    return x
}

// return a slice whose members have the same product
// as the input slice, but hopefully shorter.
func group(a []uint64) []uint64 {
    var g []uint64
    r := uint64(1)
    b := 1
    for _, x := range a {
        c := bits(x)
        if b+c <= 64 {
            r *= x
            b += c
        } else {
            g = append(g, r)
            r = x
            b = c
        }
    }
    g = append(g, r)
    return g
}

// bits returns the number of bits in the representation of x
func bits(x uint64) int {
    n := 0
    for x != 0 {
        n++
        x >>= 1
    }
    return n
}

ทำงานโดยการนับปัจจัยหลักทั้งหมดในตัวเศษและส่วนและการยกเลิกปัจจัยการจับคู่ คูณของที่เหลือเพื่อให้ได้ผลลัพธ์

เวลากว่า 80% ใช้ในการแปลงเป็นฐาน 10 มีวิธีที่ดีกว่าในการทำเช่นนั้น ...


สำหรับปัญหาที่ต้องพิมพ์จำนวนมากในฐาน 10 ฉันมักพบว่าเป็นประโยชน์ในการเขียนคลาส BigInteger ของฉันเองซึ่งเก็บหมายเลขไว้ในฐาน 1E9 ~ 2 ^ 30
ปีเตอร์เทย์เลอร์

ขณะนี้คุณชนะด้วยไมล์ระดับประเทศ .. ตามที่พวกเขาพูด

@PeterTaylor: ฉันพยายามแล้ว แต่ต้องการ% 1e9 จำนวนมากในการคูณรหัสซึ่งทำให้การคูณช้าลง
Keith Randall

6

Python 3 (8.8 = 2.2 ล้าน / 0.25 ล้าน)

นี่คือ Python ซึ่งไม่รู้จักความเร็วดังนั้นคุณอาจจะทำได้ดีกว่าในการเปลี่ยนภาษานี้เป็นภาษาอื่น

เฉพาะเครื่องกำเนิดไฟฟ้าที่นำมาจากนี้การประกวด StackOverflow

import numpy
import time

def primesfrom2to(n):
    """ Input n>=6, Returns a array of primes, 2 <= p < n """
    sieve = numpy.ones(n//3 + (n%6==2), dtype=numpy.bool)
    for i in range(1,int(n**0.5)//3+1):
        if sieve[i]:
            k=3*i+1|1
            sieve[       k*k/3     ::2*k] = False
            sieve[k*(k-2*(i&1)+4)/3::2*k] = False
    return numpy.r_[2,3,((3*numpy.nonzero(sieve)[0][1:]+1)|1)]

t0 = time.clock()

N=220*10**4
n=N//2

print("N = %d" % N)
print()

print("Generating primes.")
primes = primesfrom2to(N)

t1 = time.clock()
print ("Time taken: %f" % (t1-t0))

print("Computing product.")
product = 1

for p in primes:
    p=int(p)
    carries = 0 
    carry = 0

    if p>n:
        product*=p
        continue

    m=n

    #Count carries of n+n in base p as per Kummer's Theorem
    while m:
        digit = m%p
        carry = (2*digit + carry >= p)
        carries += carry
        m//=p

    if carries >0:
        for _ in range(carries):
            product *= p

    #print(p,carries,product)

t2 = time.clock()
print ("Time taken: %f" % (t2-t1))

print("Converting number to string.")

# digit_sum = 0
# result=product

# while result:
    # digit_sum+=result%10
    # result//=10

digit_sum = 0
digit_string = str(product)

t3 = time.clock()
print ("Time taken: %f" % (t3-t2))

print("Summing digits.")
for d in str(digit_string):digit_sum+=int(d)

t4 = time.clock()
print ("Time taken: %f" % (t4-t3))
print ()

print ("Total time: %f" % (t4-t0))
print()
print("Sum of digits = %d" % digit_sum)

แนวคิดหลักของอัลกอริทึมคือการใช้ทฤษฎีบทของ Kummerเพื่อให้ได้ตัวประกอบกำลังสองที่สำคัญของทวินาม สำหรับนายกแต่ละคนเราเรียนรู้พลังสูงสุดของมันซึ่งแบ่งคำตอบและคูณผลิตภัณฑ์ที่กำลังวิ่งด้วยพลังของไพรม์นั้น ด้วยวิธีนี้เราต้องคูณหนึ่งครั้งสำหรับนายกแต่ละคนในการแยกตัวประกอบเฉพาะของคำตอบ

เอาท์พุทแสดงการแบ่งเวลา:

N = 2200000
Generating primes.
Time taken: 0.046408
Computing product.
Time taken: 17.931472
Converting number to string.
Time taken: 39.083390
Summing digits.
Time taken: 1.502393

Total time: 58.563664

Sum of digits = 2980107

น่าแปลกที่เวลาส่วนใหญ่ใช้การแปลงตัวเลขเป็นสตริงเพื่อหาผลรวมของตัวเลข นอกจากนี้ที่น่าประหลาดใจการแปลงไปยังสตริงนั้นเร็วกว่าการรับตัวเลขจากการทำซ้ำ%10และ//10แม้ว่าสตริงทั้งหมดจะต้องถูกเก็บไว้ในหน่วยความจำ

การสร้างจำนวนเฉพาะต้องใช้เวลาเล็กน้อย (และด้วยเหตุนี้ฉันจึงไม่รู้สึกว่าการคัดลอกโค้ดที่ไม่เป็นธรรม) การรวมตัวเลขอย่างรวดเร็ว การคูณจริงใช้เวลาหนึ่งในสามของเวลา

เนื่องจากการสรุปตัวเลขนั้นดูเหมือนจะเป็นปัจจัย จำกัด บางทีอัลกอริทึมในการคูณตัวเลขในการแทนทศนิยมจะช่วยประหยัดเวลาโดยรวมด้วยการลัดการแปลงไบนารี / ทศนิยม


สิ่งนี้น่าประทับใจมากและทำให้คุณสงสัยว่าทำไม cpython ไม่ใช้การติดตั้งของคุณ!

3

Java (คะแนน 22500/365000 = 0.062)

ฉันไม่มี Python ในเครื่องนี้ดังนั้นถ้าใครสามารถให้คะแนนนี้ฉันก็จะขอบคุณ ถ้าไม่มันจะต้องรอ

พื้นฐานของการใช้งานนี้คือ

(2nn)=k=0n(nk)2

คอขวดคือส่วนเพิ่มเติมในการคำนวณส่วนที่เกี่ยวข้องของสามเหลี่ยมปาสคาล (90% ของเวลาทำงาน) ดังนั้นการใช้อัลกอริทึมการคูณที่ดีขึ้นจะไม่ช่วยได้จริงๆ

โปรดทราบว่าสิ่งที่เรียกคำถามคือสิ่งที่ผมเรียกn อาร์กิวเมนต์บรรทัดคำสั่งคือสิ่งที่โทรคำถาม2nn

public class CodeGolf37270 {
    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println("Usage: java CodeGolf37270 <n>");
            System.exit(1);
        }

        int two_n = Integer.parseInt(args[0]);
        // \binom{2n}{n} = \sum_{k=0}^n \binom{n}{k}^2
        // Two cases:
        //   n = 2m: \binom{4m}{2m} = \binom{2m}{m}^2 + 2\sum_{k=0}^{m-1} \binom{2m}{k}^2
        //   n = 2m+1: \binom{4m+2}{2m+1} = 2\sum_{k=0}^{m} \binom{2m+1}{k}^2
        int n = two_n / 2;
        BigInt[] nCk = new BigInt[n/2 + 1];
        nCk[0] = new BigInt(1);
        for (int k = 1; k < nCk.length; k++) nCk[k] = nCk[0];
        for (int row = 2; row <= n; row++) {
            BigInt tmp = nCk[0];
            for (int col = 1; col < row && col < nCk.length; col++) {
                BigInt replacement = tmp.add(nCk[col]);
                tmp = nCk[col];
                nCk[col] = replacement;
            }
        }

        BigInt central = nCk[0]; // 1^2 = 1
        int lim = (n & 1) == 1 ? nCk.length : (nCk.length - 1);
        for (int k = 1; k < lim; k++) central = central.add(nCk[k].sq());
        central = central.add(central);
        if ((n & 1) == 0) central = central.add(nCk[nCk.length - 1].sq());

        System.out.println(central.digsum());
    }

    private static class BigInt {
        static final int B = 1000000000;
        private int[] val;

        public BigInt(int x) {
            val = new int[] { x };
        }

        private BigInt(int[] val) {
            this.val = val;
        }

        public BigInt add(BigInt that) {
            int[] left, right;
            if (val.length < that.val.length) {
                left = that.val;
                right = val;
            }
            else {
                left = val;
                right = that.val;
            }

            int[] sum = left.clone();
            int carry = 0, k = 0;
            for (; k < right.length; k++) {
                int a = sum[k] + right[k] + carry;
                sum[k] = a % B;
                carry = a / B;
            }
            while (carry > 0 && k < sum.length) {
                int a = sum[k] + carry;
                sum[k] = a % B;
                carry = a / B;
                k++;
            }
            if (carry > 0) {
                int[] wider = new int[sum.length + 1];
                System.arraycopy(sum, 0, wider, 0, sum.length);
                wider[sum.length] = carry;
                sum = wider;
            }

            return new BigInt(sum);
        }

        public BigInt sq() {
            int[] rv = new int[2 * val.length];
            // Naive multiplication
            for (int i = 0; i < val.length; i++) {
                for (int j = i; j < val.length; j++) {
                    int k = i+j;
                    long c = val[i] * (long)val[j];
                    if (j > i) c <<= 1;
                    while (c > 0) {
                        c += rv[k];
                        rv[k] = (int)(c % B);
                        c /= B;
                        k++;
                    }
                }
            }

            int len = rv.length;
            while (len > 1 && rv[len - 1] == 0) len--;
            if (len < rv.length) {
                int[] rv2 = new int[len];
                System.arraycopy(rv, 0, rv2, 0, len);
                rv = rv2;
            }

            return new BigInt(rv);
        }

        public long digsum() {
            long rv = 0;
            for (int i = 0; i < val.length; i++) {
                int x = val[i];
                while (x > 0) {
                    rv += x % 10;
                    x /= 10;
                }
            }
            return rv;
        }
    }
}

ฉันได้รับ 29,500 สำหรับโปรแกรมของคุณและ 440,000 สำหรับโปรแกรมอ้างอิงดังนั้นนั่นจะเป็นคะแนน 0.067 นี่เป็นการรวบรวมด้วย Java 1.7 ( javac CodeGolf37270.java) และการดำเนินการกับ Java 1.8 ( java CodeGolf37270 n) ฉันไม่แน่ใจว่ามีตัวเลือกการเพิ่มประสิทธิภาพที่ฉันไม่รู้จักหรือไม่ ฉันไม่สามารถลองรวบรวมกับ Java 1.8 เพราะมันไม่ได้รับการติดตั้งกับแพคเกจ Java ของฉัน ...
เดนนิส

แนวทางที่น่าสนใจ ทำไมคุณคิดว่าการคำนวณอย่างซ้ำ ๆ อาจเร็วกว่าการใช้สูตรอย่างง่าย
justhalf

@ justhalf ฉันไม่มีสัญชาตญาณว่าจะเร็วขึ้นหรือไม่และฉันไม่ได้พยายามคำนวณความซับซ้อน ฉันค้นหารายการของตัวตนสำหรับค่าสัมประสิทธิ์ทวินามกลางเพื่อพยายามหาสูตรที่ง่ายต่อการใช้งานด้วยคลาสจำนวนเต็มขนาดใหญ่ที่กำหนดเองซึ่งเหมาะสำหรับการแยกฐาน -10 หลัก และเมื่อค้นพบว่ามันไม่ได้มีประสิทธิภาพมากนักฉันอาจโพสต์และช่วยคนอื่นจากการทดสอบซ้ำ (FWIW ฉันกำลังทำงานกับการทวีคูณ Toom แต่ฉันไม่แน่ใจว่าเมื่อไหร่ฉันจะทดสอบและดีบั๊ก)
Peter Taylor

2

GMP - 1500000/300000 = 5.0

แม้ว่าคำตอบนี้จะไม่แข่งขันกับ sieves บางครั้งรหัสย่อยังสามารถรับผลลัพธ์ได้

#include <gmpxx.h>
#include <iostream>

mpz_class sum_digits(mpz_class n)
{
    char* str = mpz_get_str(NULL, 10, n.get_mpz_t());
    int result = 0;
    for(int i=0; str[i]>0; i++)

    result += str[i] - 48;

    return result;
}


mpz_class comb_2(const mpz_class &x)
{
    const unsigned int k = mpz_get_ui(x.get_mpz_t()) / 2;
    mpz_class result = k + 1;

    for(int i=2; i<=k; i++)
    {
        result *= k + i;
        mpz_divexact_ui(result.get_mpz_t(), result.get_mpz_t(), i);
    }

    return result;
}

int main()
{
    const mpz_class n = 1500000;
    std::cout << sum_digits(comb_2(n)) << std::endl;

    return 0;
}

2

Java, คลาสจำนวนเต็มขนาดใหญ่ที่กำหนดเอง: 32.9 (120000000/365000)

ชั้นหลักค่อนข้างตรงไปตรงมา:

import java.util.*;

public class PPCG37270 {
    public static void main(String[] args) {
        long start = System.nanoTime();

        int n = 12000000;
        if (args.length == 1) n = Integer.parseInt(args[0]);

        boolean[] sieve = new boolean[n + 1];
        int[] remaining = new int[n + 1];
        int[] count = new int[n + 1];

        for (int p = 2; p <= n; p++) {
            if (sieve[p]) continue;
            long p2 = p * (long)p;
            if (p2 > n) continue;
            for (int i = (int)p2; i <= n; i += p) sieve[i] = true;
        }

        for (int i = 2; i <= n; i++) remaining[i] = i;
        for (int p = 2; p <= n; p++) {
            if (sieve[p]) continue;
            for (int i = p; i <= n; i += p) {
                while (remaining[i] % p == 0) {
                    remaining[i] /= p;
                    count[p]++;
                    if (i <= n/2) count[p] -= 2;
                }
            }
        }

        count[2] -= count[5];
        count[5] = 0;

        List<BigInt> partialProd = new ArrayList<BigInt>();
        long accum = 1;
        for (int i = 2; i <= n; i++) {
            for (int j = count[i]; j > 0; j--) {
                long tmp = accum * i;
                if (tmp < 1000000000L) accum = tmp;
                else {
                    partialProd.add(new BigInt((int)accum));
                    accum = i;
                }
            }
        }
        partialProd.add(new BigInt((int)accum));
        System.out.println(prod(partialProd).digsum());
        System.out.println((System.nanoTime() - start) / 1000000 + "ms");
    }

    private static BigInt prod(List<BigInt> vals) {
        while (vals.size() > 1) {
            int n = vals.size();
            List<BigInt> next = new ArrayList<BigInt>();
            for (int i = 0; i < n; i += 2) {
                if (i == n - 1) next.add(vals.get(i));
                else next.add(vals.get(i).mul(vals.get(i+1)));
            }
            vals = next;
        }
        return vals.get(0);
    }
}

มันอาศัยชั้นจำนวนเต็มขนาดใหญ่ที่เหมาะสำหรับการคูณและทั้งสองซึ่งเป็นคอขวดที่สำคัญในการดำเนินการกับtoString()java.math.BigInteger

/**
 * A big integer class which is optimised for conversion to decimal.
 * For use in simple applications where BigInteger.toString() is a bottleneck.
 */
public class BigInt {
    // The base of the representation.
    private static final int B = 1000000000;
    // The number of decimal digits per digit of the representation.
    private static final int LOG10_B = 9;

    public static final BigInt ZERO = new BigInt(0);
    public static final BigInt ONE = new BigInt(1);

    // We use sign-magnitude representation.
    private final boolean negative;

    // Least significant digit is at val[off]; most significant is at val[off + len - 1]
    // Unless len == 1 we guarantee that val[off + len - 1] is non-zero.
    private final int[] val;
    private final int off;
    private final int len;

    // Toom-style multiplication parameters from
    // Zuras, D. (1994). More on squaring and multiplying large integers. IEEE Transactions on Computers, 43(8), 899-908.
    private static final int[][][] Q = new int[][][]{
        {},
        {},
        {{1, -1}},
        {{4, 2, 1}, {1, 1, 1}, {1, 2, 4}},
        {{8, 4, 2, 1}, {-8, 4, -2, 1}, {1, 1, 1, 1}, {1, -2, 4, -8}, {1, 2, 4, 8}}
    };
    private static final int[][][] R = new int[][][]{
        {},
        {},
        {{1, -1, 1}},
        {{-21, 2, -12, 1, -6}, {7, -1, 10, -1, 7}, {-6, 1, -12, 2, -21}},
        {{-180, 6, 2, -80, 1, 3, -180}, {-510, 4, 4, 0, -1, -1, 120}, {1530, -27, -7, 680, -7, -27, 1530}, {120, -1, -1, 0, 4, 4, -510}, {-180, 3, 1, -80, 2, 6, -180}}
    };
    private static final int[][] S = new int[][]{
        {},
        {},
        {1, 1, 1},
        {1, 6, 2, 6, 1},
        {1, 180, 120, 360, 120, 180, 1}
    };

    /**
     * Constructs a big version of an integer value.
     * @param x The value to represent.
     */
    public BigInt(int x) {
        this(Integer.toString(x));
    }

    /**
     * Constructs a big version of a long value.
     * @param x The value to represent.
     */
    public BigInt(long x) {
        this(Long.toString(x));
    }

    /**
     * Parses a decimal representation of an integer.
     * @param str The value to represent.
     */
    public BigInt(String str) {
        this(str.charAt(0) == '-', split(str));
    }

    /**
     * Constructs a sign-magnitude representation taking the entire span of the array as the range of interest.
     * @param neg Is the value negative?
     * @param val The base-B digits, least significant first.
     */
    private BigInt(boolean neg, int[] val) {
        this(neg, val, 0, val.length);
    }

    /**
     * Constructs a sign-magnitude representation taking a range of an array as the magnitude.
     * @param neg Is the value negative?
     * @param val The base-B digits, least significant at offset off, most significant at off + val - 1.
     * @param off The offset within the array.
     * @param len The number of base-B digits.
     */
    private BigInt(boolean neg, int[] val, int off, int len) {
        // Bounds checks
        if (val == null) throw new IllegalArgumentException("val");
        if (off < 0 || off >= val.length) throw new IllegalArgumentException("off");
        if (len < 1 || off + len > val.length) throw new IllegalArgumentException("len");

        this.negative = neg;
        this.val = val;
        this.off = off;
        // Enforce the invariant that this.len is 1 or val[off + len - 1] is non-zero.
        while (len > 1 && val[off + len - 1] == 0) len--;
        this.len = len;

        // Sanity check
        for (int i = 0; i < len; i++) {
            if (val[off + i] < 0) throw new IllegalArgumentException("val contains negative digits");
        }
    }

    /**
     * Splits a string into base-B digits.
     * @param str The string to parse.
     * @return An array which can be passed to the (boolean, int[]) constructor.
     */
    private static int[] split(String str) {
        if (str.charAt(0) == '-') str = str.substring(1);

        int[] arr = new int[(str.length() + LOG10_B - 1) / LOG10_B];
        int i, off;
        // Each element of arr represents LOG10_B characters except (probably) the last one.
        for (i = 0, off = str.length() - LOG10_B; off > 0; off -= LOG10_B) {
            arr[i++] = Integer.parseInt(str.substring(off, off + LOG10_B));
        }
        arr[i] = Integer.parseInt(str.substring(0, off + LOG10_B));
        return arr;
    }

    public boolean isZero() {
        return len == 1 && val[off] == 0;
    }

    public BigInt negate() {
        return new BigInt(!negative, val, off, len);
    }

    public BigInt add(BigInt that) {
        // If the signs differ, then since we use sign-magnitude representation we want to do a subtraction.
        boolean isSubtraction = negative ^ that.negative;

        BigInt left, right;
        if (len < that.len) {
            left = that;
            right = this;
        }
        else {
            left = this;
            right = that;

            // For addition I just care about the lengths of the arrays.
            // For subtraction I want the largest absolute value on the left.
            if (isSubtraction && len == that.len) {
                int cmp = compareAbsolute(that);
                if (cmp == 0) return ZERO; // Cheap special case
                if (cmp < 0) {
                    left = that;
                    right = this;
                }
            }
        }

        if (right.isZero()) return left;

        BigInt result;
        if (!isSubtraction) {
            int[] sum = new int[left.len + 1];
            // A copy here rather than using left.val in the main loops and copying remaining values
            // at the end gives a small performance boost, probably due to cache locality.
            System.arraycopy(left.val, left.off, sum, 0, left.len);

            int carry = 0, k = 0;
            for (; k < right.len; k++) {
                int a = sum[k] + right.val[right.off + k] + carry;
                sum[k] = a % B;
                carry = a / B;
            }
            for (; carry > 0 && k < left.len; k++) {
                int a = sum[k] + carry;
                sum[k] = a % B;
                carry = a / B;
            }
            sum[left.len] = carry;

            result = new BigInt(negative, sum);
        }
        else {
            int[] diff = new int[left.len];
            System.arraycopy(left.val, left.off, diff, 0, left.len);

            int carry = 0, k = 0;
            for (; k < right.len; k++) {
                int a = diff[k] - right.val[right.off + k] + carry;
                // Why did anyone ever think that rounding positive and negative divisions differently made sense?
                if (a < 0) {
                    diff[k] = a + B;
                    carry = -1;
                }
                else {
                    diff[k] = a % B;
                    carry = a / B;
                }
            }
            for (; carry != 0 && k < left.len; k++) {
                int a = diff[k] + carry;
                if (a < 0) {
                    diff[k] = a + B;
                    carry = -1;
                }
                else {
                    diff[k] = a % B;
                    carry = a / B;
                }
            }

            result = new BigInt(left.negative, diff, 0, k > left.len ? k : left.len);
        }

        return result;
    }

    private int compareAbsolute(BigInt that) {
        if (len > that.len) return 1;
        if (len < that.len) return -1;

        for (int i = len - 1; i >= 0; i--) {
            if (val[off + i] > that.val[that.off + i]) return 1;
            if (val[off + i] < that.val[that.off + i]) return -1;
        }

        return 0;
    }

    public BigInt mul(BigInt that) {
        if (isZero() || that.isZero()) return ZERO;

        if (len == 1) return that.mulSmall(negative ? -val[off] : val[off]);
        if (that.len == 1) return mulSmall(that.negative ? -that.val[that.off] : that.val[that.off]);

        int shorter = len < that.len ? len : that.len;
        BigInt result;
        // Cutoffs have been hand-tuned.
        if (shorter > 300) result = mulToom(3, that);
        else if (shorter > 28) result = mulToom(2, that);
        else result = mulNaive(that);

        return result;
    }

    BigInt mulSmall(int m) {
        if (m == 0) return ZERO;
        if (m == 1) return this;
        if (m == -1) return negate();

        // We want to do the magnitude calculation with a positive multiplicand.
        boolean neg = negative;
        if (m < 0) {
            neg = !neg;
            m = -m;
        }

        int[] pr = new int[len + 1];
        int carry = 0;
        for (int i = 0; i < len; i++) {
            long t = val[off + i] * (long)m + carry;
            pr[i] = (int)(t % B);
            carry = (int)(t / B);
        }
        pr[len] = carry;
        return new BigInt(neg, pr);
    }

    // NB This truncates.
    BigInt divSmall(int d) {
        if (d == 0) throw new ArithmeticException();
        if (d == 1) return this;
        if (d == -1) return negate();

        // We want to do the magnitude calculation with a positive divisor.
        boolean neg = negative;
        if (d < 0) {
            neg = !neg;
            d = -d;
        }

        int[] div = new int[len];
        int rem = 0;
        for (int i = len - 1; i >= 0; i--) {
            long t = val[off + i] + rem * (long)B;
            div[i] = (int)(t / d);
            rem = (int)(t % d);
        }

        return new BigInt(neg, div);
    }

    BigInt mulNaive(BigInt that) {
        int[] rv = new int[len + that.len];
        // Naive multiplication
        for (int i = 0; i < len; i++) {
            for (int j = 0; j < that.len; j++) {
                int k = i + j;
                long c = val[off + i] * (long)that.val[that.off + j];
                while (c > 0) {
                    c += rv[k];
                    rv[k] = (int)(c % B);
                    c /= B;
                    k++;
                }
            }
        }

        return new BigInt(this.negative ^ that.negative, rv);
    }

    private BigInt mulToom(int k, BigInt that) {
        // We split each number into k parts of m base-B digits each.
        // m = ceil(longer / k)
        int m = ((len > that.len ? len : that.len) + k - 1) / k;

        // Perform the splitting and evaluation steps of Toom-Cook.
        BigInt[] f1 = this.toomFwd(k, m);
        BigInt[] f2 = that.toomFwd(k, m);

        // Pointwise multiplication.
        for (int i = 0; i < f1.length; i++) f1[i] = f1[i].mul(f2[i]);

        // Inverse (or interpolation) and recomposition.
        return toomBk(k, m, f1, negative ^ that.negative, val[off], that.val[that.off]);
    }

    // Splits a number into k parts of m base-B digits each and does the polynomial evaluation.
    private BigInt[] toomFwd(int k, int m) {
        // Split.
        BigInt[] a = new BigInt[k];
        for (int i = 0; i < k; i++) {
            int o = i * m;
            if (o >= len) a[i] = ZERO;
            else {
                int l = m;
                if (o + l > len) l = len - o;
                // Ignore signs for now.
                a[i] = new BigInt(false, val, off + o, l);
            }
        }

        // Evaluate
        return transform(Q[k], a);
    }

    private BigInt toomBk(int k, int m, BigInt[] f, boolean neg, int lsd1, int lsd2) {
        // Inverse (or interpolation).
        BigInt[] b = transform(R[k], f);

        // Recomposition: add at suitable offsets, dividing by the normalisation factors
        BigInt prod = ZERO;
        int[] s = S[k];
        for (int i = 0; i < b.length; i++) {
            int[] shifted = new int[i * m + b[i].len];
            System.arraycopy(b[i].val, b[i].off, shifted, i * m, b[i].len);
            prod = prod.add(new BigInt(neg ^ b[i].negative, shifted).divSmall(s[i]));
        }

        // Handle the remainders.
        // In the worst case the absolute value of the sum of the remainders is s.length, so pretty small.
        // It should be easy enough to work out whether to go up or down.
        int lsd = (int)((lsd1 * (long)lsd2) % B);
        int err = lsd - prod.val[prod.off];
        if (err > B / 2) err -= B / 2;
        if (err < -B / 2) err += B / 2;
        return prod.add(new BigInt(err));
    }

    /**
     * Multiplies a matrix of small integers and a vector of big ones.
     * The matrix has a implicit leading row [1 0 ... 0] and an implicit trailing row [0 ... 0 1].
     * @param m The matrix.
     * @param v The vector.
     * @return m v
     */
    private BigInt[] transform(int[][] m, BigInt[] v) {
        BigInt[] b = new BigInt[m.length + 2];
        b[0] = v[0];
        for (int i = 0; i < m.length; i++) {
            BigInt s = ZERO;
            for (int j = 0; j < m[i].length; j++) s = s.add(v[j].mulSmall(m[i][j]));
            b[i + 1] = s;
        }
        b[b.length - 1] = v[v.length - 1];

        return b;
    }

    /**
     * Sums the digits of this integer.
     * @return The sum of the digits of this integer.
     */
    public long digsum() {
        long rv = 0;
        for (int i = 0; i < len; i++) {
            int x = val[off + i];
            while (x > 0) {
                rv += x % 10;
                x /= 10;
            }
        }
        return rv;
    }
}

คอขวดขนาดใหญ่คือการคูณไร้เดียงสา (60%) ตามด้วยการคูณอื่น ๆ (37%) และการร่อน (3%) การdigsum()โทรไม่มีนัยสำคัญ

วัดประสิทธิภาพด้วย OpenJDK 7 (64 บิต)


ดีมาก. ขอขอบคุณ.

1

Python 2 (PyPy), 1,134,000 / 486,000 = 2.32

#/!usr/bin/pypy
n=input(); a, b, c=1, 1, 2**((n+2)/4)
for i in range(n-1, n/2, -2): a*=i
for i in range(2, n/4+1): b*=i
print sum(map(int, str(a*c/b)))

ผลลัพธ์: 1,537,506

เรื่องสนุก: คอขวดของรหัสของคุณกำลังเพิ่มตัวเลขไม่ใช่การคำนวณสัมประสิทธิ์ทวินาม


ทำไมไพ ธ อนจึงต้องเพิ่มจำนวนอย่างช้า ๆ ? ทั้งคุณและ xnor บอกว่ามันเป็น มันทำให้ฉันอยากรู้อยากเห็นดังนั้นฉันโอเวอร์คล็อกของฉัน มันมาในเวลาน้อยกว่าหนึ่งวินาทีสำหรับส่วนผลรวม (Java)
Geobits

@Geobits อืมอยากรู้อยากเห็น Java สามารถทำการแปลงเลขฐานสองแบบทศนิยมอย่างรวดเร็วเช่นเดียวกันหรือไม่ มันเป็นตัวแทนจำนวนเต็มในเลขฐานสองใช่มั้ย
xnor

นั่นเป็นคำถามที่ดี สำหรับจำนวนเต็ม / จำนวนเต็ม / ยาว / ยาวฉันรู้ว่ามันเป็นเลขฐานสอง ฉันไม่แน่ใจว่าการเป็นตัวแทนภายในของ BigInteger คืออะไร ถ้าเป็นทศนิยมนั่นจะอธิบายได้อย่างชัดเจนว่าทำไมคณิตศาสตร์ถึงช้า แต่แปลงเป็นสตริงได้อย่างรวดเร็ว อาจมองว่าพรุ่งนี้
Geobits

@Geobits การเป็นตัวแทนภายในของ BigInteger นั้นเป็นฐานที่ 2
Peter Taylor

ฉันคิดเสมอ แต่มันทำให้ฉันสงสัย ดูเหมือนว่าจะแบ่งมันเป็นชิ้นขนาดยาวและแปลงอย่างนั้นอย่างน้อยใน OpenJDK
Geobits

1

Java (2,020,000 / 491,000) = 4.11

อัปเดตก่อนหน้า 2.24

Java BigIntegerไม่ใช่ตัวเร่งหมายเลขเร็วที่สุด แต่ก็ดีกว่าไม่มีอะไรเลย

สูตรพื้นฐานสำหรับสิ่งนี้ดูเหมือนจะเป็นn! / ((n/2)!^2)แต่ดูเหมือนว่าจะเป็นการคูณซ้ำซ้อน

คุณสามารถเร่งความเร็วได้อย่างมีนัยสำคัญโดยกำจัดปัจจัยสำคัญทั้งหมดที่พบในตัวเศษและส่วน เมื่อต้องการทำเช่นนี้ฉันจะใช้ตะแกรงที่เรียบง่ายก่อนอื่น จากนั้นสำหรับนายกแต่ละคนฉันจะนับว่าต้องยกระดับพลังอะไร เพิ่มขึ้นทุกครั้งที่ฉันเห็นปัจจัยในตัวเศษลดลงสำหรับตัวส่วน

ฉันจัดการสองครั้งแยกกัน (และก่อน) เนื่องจากมันง่ายที่จะนับ / กำจัดพวกเขาก่อนที่จะแยกตัวประกอบ

หลังจากที่ทำคุณจะมีจำนวนเงินขั้นต่ำของคูณที่จำเป็นซึ่งเป็นสิ่งที่ดีเพราะ bigint คูณเป็นช้า

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

public class CentBiCo {
    public static void main(String[] args) {
        int n = 2020000;
        long time = System.currentTimeMillis();
        sieve(n);
        System.out.println(sumDigits(cbc(n)));
        System.out.println(System.currentTimeMillis()-time);
    }

    static boolean[] sieve;
    static List<Integer> primes;
    static void sieve(int n){
        primes = new ArrayList<Integer>((int)(Math.sqrt(n)));
        sieve = new boolean[n];
        sieve[2]=true;
        for(int i=3;i<sieve.length;i+=2)
            if(i%2==1)
                sieve[i] = true;
        for(int i=3;i<sieve.length;i+=2){
            if(!sieve[i])
                continue;
            for(int j=i*2;j<sieve.length;j+=i)
                sieve[j] = false;
        }
        for(int i=2;i<sieve.length;i++)
            if(sieve[i])
                primes.add(i);
    }

    static int[] factors;
    static void addFactors(int n, int flip){
        for(int k=0;primes.get(k)<=n;){
            int i = primes.get(k);
            if(n%i==0){
                factors[i] += flip;
                n /= i;
            } else {
                if(++k == primes.size())
                    break;
            }
        }
        factors[n]++;
    }

    static BigInteger cbc(int n){
        factors = new int[n+1];
        int x = n/2;
        for(int i=x%2<1?x+1:x+2;i<n;i+=2)
            addFactors(i,1);
        factors[2] = x;
        for(int i=1;i<=x/2;i++){
            int j=i;
            while(j%2<1 && factors[2] > 1){
                j=j/2;
                factors[2]--;
            }
            addFactors(j,-1);
            factors[2]--;
        }
        BigInteger cbc = BigInteger.ONE;
        for(int i=3;i<factors.length;i++){
            if(factors[i]>0)
                cbc = cbc.multiply(BigInteger.valueOf(i).pow(factors[i]));
        }
        return cbc.shiftLeft(factors[2]);
    }

    static long sumDigits(BigInteger in){
        long sum = 0;
        String str = in.toString();
        for(int i=0;i<str.length();i++)
            sum += str.charAt(i)-'0';
        return sum;
    }
}

โอ้และผลรวมเอาต์พุตสำหรับ n = 2020000 คือ2735298เพื่อการตรวจสอบ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.