OEIS A005434 계산


10

과제는 가능한 빨리 OEIS A005434 를 계산하는 것입니다 .

Slength 의 이진 문자열 을 고려하십시오 n. 에서 색인 1우리가 경우에 결정할 수, S[1..i+1]경기 S[n-i..n]정확하게 모두를 i에서 순서 0n-1. 예를 들어

S = 01010

준다

[Y, N, Y, N, Y].

이 때문입니다 0일치 0, 01일치하지 않는 10, 010일치 010, 0101일치하지 않는 1010 그리고 마지막으로 01010자신을 일치합니다.

길이가 다른 가능한 모든 비트 문자열 을 반복 할 때 얻는 s 및 s f(n)의 개별 배열 수로 정의하십시오 .YN2^nSn

관찰자는이 질문이 최근또 다른 질문에 대한 간단한 변형이라는 것을 알게 될 입니다. 그러나 나는 영리한 트릭으로 훨씬 빠르고 쉽게 할 수 있다고 생각합니다.

직무

n시작 시간을 늘리 1려면 코드가 출력되어야합니다 n, f(n).

답변 예

에 대한 n = 1..24정답은 다음과 같습니다.

1, 2, 3, 4, 6, 8, 10, 13, 17, 21, 27, 30, 37, 47, 57, 62, 75, 87, 102, 116, 135, 155, 180, 194

채점

코드는 차례로 n = 1각각 n에 대한 답변을 제공하는 것부터 반복해야합니다 . 나는 전체 실행 시간을 정하고 2 분 후에 죽일 것이다.

n그 시간에 당신 의 점수가 가장 높습니다 .

동점 인 경우 첫 번째 답이 이깁니다.

내 코드는 어디에서 테스트됩니까?

Lubuntu 게스트 VM (Windows 7 호스트) 의 Virtualbox 에서 코드를 실행합니다 .

내 노트북에는 8GB의 RAM과 2 개의 코어와 4 개의 스레드가있는 Intel i7 5600U@2.6GHz (Broadwell) CPU가 있습니다. 명령어 세트에는 SSE4.2, AVX, AVX2, FMA3 및 TSX가 포함됩니다.

언어 별 주요 항목

  • Rust bu Anders Kaseorg 에서 n = 599
  • Grimy에 의해 C 에서 n = 30 이다 . cygwin에서 기본적으로 실행될 때 병렬 버전은 32가 됩니다.

-O3으로 실행되는 math.uni-bielefeld.de/~sillke/SEQUENCES/autocorrelation-range.c(OEIS 페이지에서 링크)는 내 컴퓨터에서 <.02 초 안에 최대 100 개까지 계산할 수 있습니다
vroomfondel

트윗 담아 가기 질문을 삭제해야하지만 이미 답변이 있습니다.

나는 여전히 멋진 문제라고 생각하지만 대신 1000까지 가능합니까? 또는 골프에 충분히 빠른 프로그램에 대한 답변을 요청하십시오
vroomfondel

1
@ rogaos 방금 하드 한계를 완전히 제거했습니다.

답변:


4

, n ≈ 660

use std::collections::HashMap;
use std::iter::once;
use std::rc::Rc;

type Memo = HashMap<(u32, u32, Rc<Vec<u32>>), u64>;

fn f(memo: &mut Memo, mut n: u32, p: u32, mut s: Rc<Vec<u32>>) -> u64 {
    debug_assert!(p != 0);
    let d = n / p;
    debug_assert!(d >= 1);
    let r = n - p * if d >= 2 { d - 1 } else { 1 };

    let k = s.binary_search(&(n - r + 1)).unwrap_or_else(|i| i);
    for &i in &s[..k] {
        if i % p != 0 {
            return 0;
        }
    }

    if d >= 3 {
        let o = n - (p + r);
        n = p + r;
        s = Rc::new(s[k..].iter().map(|i| i - o).collect());
    } else if n == p {
        return 1;
    } else if k != 0 {
        s = Rc::new(s[k..].to_vec());
    }

    let query = (n, p, s);
    if let Some(&c) = memo.get(&query) {
        return c;
    }
    let (n, p, s) = query;

    let t = Rc::new(s.iter().map(|i| i - p).collect::<Vec<_>>());
    let c = if d < 2 {
        (1..r + 1).map(|q| f(memo, r, q, t.clone())).sum()
    } else if r == p {
        (1..p + 1)
            .filter(|&q| p % q != 0 || q == p)
            .map(|q| f(memo, r, q, t.clone()))
            .sum()
    } else {
        let t = match t.binary_search(&p) {
            Ok(_) => t,
            Err(k) => {
                Rc::new(t[..k]
                            .iter()
                            .cloned()
                            .chain(once(p))
                            .chain(t[k..].iter().cloned())
                            .collect::<Vec<_>>())
            }
        };
        (1..t.first().unwrap() + 1)
            .filter(|&q| p % q != 0 || q == p)
            .map(|q| f(memo, r, q, t.clone()))
            .sum()
    };
    memo.insert((n, p, s), c);
    c
}

fn main() {
    let mut memo = HashMap::new();
    let s = Rc::new(Vec::new());
    for n in 1.. {
        println!("{} {}",
                 n,
                 (1..n + 1)
                     .map(|p| f(&mut memo, n, p, s.clone()))
                     .sum::<u64>());
    }
}

온라인으로 사용해보십시오!

작동 원리

이것은 Leo Guibas, “Periods in strings” (1981)에 주어진 재귀 술어 Ξ의 메모리 화 된 구현입니다 . 이 함수 f(memo, n, p, s)n가장 작은주기를 가진 길이 와 p세트의 각 주기 의 상관 수를 찾습니다 s.


다른 관련 문제에 대한 더 빠른 해결책이 있는지 궁금해합니다. 매우 인상적!

흥미롭게도 이것은 완전히 메모리 제한적입니다. ~ 500까지 속도를 내고 RAM이 부족 해지면 갑자기 속도가 느려집니다.

2

도전을 시작하기위한 간단한 무차별 대입 검색 :

#include <stdio.h>
#include <stdint.h>
#include <string.h>

typedef uint16_t u16;
typedef uint64_t u64;

static u64 map[1<<16];

int main(void)
{
    for (u64 n = 1;; ++n) {
        u64 result = 1;
        u64 mask = (1ul << n) - 1;
        memset(map, 0, sizeof(map));

        #pragma omp parallel
        #pragma omp for
        for (u64 x = 1ul << (n - 1); x < 1ul << n; ++x) {

            u64 r = 0;
            for (u64 i = 1; i < n; ++i)
                r |= (u64) (x >> i == (x & (mask >> i))) << i;
            if (!r)
                continue;

            u16 h = (u16) (r ^ r >> 13 ^ r >> 27);
            while (map[h] && map[h] != r)
                ++h;

            if (!map[h]) {
                #pragma omp critical
                if (!map[h]) {
                    map[h] = r;
                    ++result;
                }
            }
        }

        printf("%ld\n", result);
    }
}

로 컴파일하십시오 clang -fopenmp -Weverything -O3 -march=native. 내 컴퓨터에서는 2 분 안에 n = 34에 도달합니다.

편집 : 쉬운 병렬 처리를 위해 일부 OMP 지시문을 뿌렸습니다.


@Lembik 삭제에 대한 SE 근거 이외의 좋은 답변이 있습니까? 누군가 (아마도 주석가)가이 알고리즘을 답변으로 제출하고 그 답변을 수락하기를 기다리지 않아야합니까?
Grimmy

당신은 아주 좋은 지적을합니다

슬프게도 CPU에 총 두 개의 코어가 있으므로 virtualbox에서 병렬 코드를 실제로 테스트 할 수 없습니다.

cygwin에서 실행했으며 32가되었습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.