오늘의 랜덤 골프 # 3 : 정수 파티션


19

시리즈 정보

우선, 이것을 다른 코드 골프 도전과 같이 취급하고 시리즈에 대해 전혀 걱정하지 않고 대답 할 수 있습니다. 그러나 모든 과제에는 리더 보드가 있습니다. 첫 번째 게시물에서 시리즈 대한 자세한 정보와 함께 리더 보드를 찾을 수 있습니다 .

시리즈에 대한 아이디어가 많이 있지만 미래의 과제는 아직 해결되지 않았습니다. 제안 사항이 있으면 관련 샌드 박스 게시물에 알려주십시오. .

홀 3 : 정수 파티션

어려움을 조금 증가시킬 시간입니다.

양의 정수 의 파티션 은에 n합산되는 양의 정수의 다중 집합으로 정의됩니다 n. 의 경우 n = 5다음과 같은 파티션이 존재합니다.

{1,1,1,1,1}
{2,1,1,1}
{2,2,1}
{3,1,1}
{3,2}
{4,1}
{5}

참고 이러한 멀티 세트는, 그래서 순서가 그들에게이 없다는 것을, {3,1,1}, {1,3,1}{1,1,3} 모두 동일한 것으로 간주됩니다.

의 작업에 n의 임의 파티션을 생성해야합니다 n. 자세한 규칙은 다음과 같습니다.

  • 생성 된 파티션의 분포는 균일 해야합니다 . 즉, 위의 예에서 각 파티션은 1/7 확률로 반환되어야합니다.

    물론 PRNG의 기술적 한계로 인해 완벽한 균일 성은 불가능합니다. 제출물의 균일 성을 평가하기 위해 다음 작업은 완전히 균일 한 분포를 산출하는 것으로 간주됩니다.

    • PRNG (모든 범위에서)에서 숫자를 얻습니다.이 숫자는 (대략) 균일 한 것으로 문서화되어 있습니다.
    • 모듈로나 곱셈 (또는 값을 균등하게 분배하는 다른 연산)을 통해 더 큰 세트의 숫자에 대한 균일 한 분포를 더 작은 세트에 매핑합니다. 더 큰 세트는 더 작은 세트보다 가능한 많은 값을 1024 배 이상 포함해야합니다.
  • 파티션은 다중 집합이므로 어떤 순서로든 반환 할 수 있으며이 순서는 일관되지 않아도됩니다. 그러나 임의 분포의 목적으로 순서는 무시됩니다. 즉, 위의 예에서, {3,1,1}, {1,3,1}{1,1,3} 서로 리턴되는 1/7의 확률을 가져야한다.

  • 알고리즘에는 결정적인 런타임 이 있어야합니다 . 특히 임의의 다중 집합을 생성 할 수 없으며 합쳐지지 않으면 거부 할 수 없습니다n .
  • 알고리즘의 시간 복잡도는 다항식이어야합니다 n. 특히 파티션의 수가 기하 급수적으로 증가하므로 모든 파티션을 생성하고 임의의 파티션을 선택할 수는 없습니다 n. 사용중인 PRNG가 값당 O (1)로 균일하게 분포 된 값을 반환 할 수 있다고 가정 할 수 있습니다.
  • 이 작업을 해결하는 내장 기능을 사용해서는 안됩니다.

전체 프로그램 또는 함수를 작성하고 STDIN 또는 가장 가까운 대체, 명령 행 인수 또는 함수 인수를 통해 입력을 가져오고 리턴 값을 통해 또는 STDOUT (또는 가장 가까운 대안)으로 인쇄하여 출력을 생성 할 수 있습니다.

n ≤ 65(파티션의 수가 2 21 보다 작도록) 가정 할 수 있습니다 . 출력은 편리하고 명확한 목록 또는 문자열 형식 일 수 있습니다.

함수를 제출하는 경우 함수를 여러 번 호출하고 결과를 인쇄하는 작은 테스트 프로그램을 제공하는 것도 고려하십시오. 코드에서 매개 변수를 조정해야한다면 괜찮습니다. 이것은 사람들이 솔루션이 적어도 대략 균일하다는 것을 확인할 수 있도록하기위한 것입니다.

이것은 코드 골프이므로 가장 짧은 제출 (바이트)이 이깁니다. 물론 사용자 당 가장 짧은 제출물도 시리즈의 전체 순위표에 들어갑니다.

리더 보드

시리즈의 첫 번째 게시물은 리더 보드를 생성합니다.

답변이 표시되도록하려면 다음 마크 다운 템플릿을 사용하여 모든 답변을 헤드 라인으로 시작하십시오.

# Language Name, N bytes

N제출물의 크기는 어디에 있습니까 ? 당신이 당신의 점수를 향상시킬 경우에, 당신은 할 수 있습니다 를 통해 눈에 띄는에 의해, 헤드 라인에 오래된 점수를 유지한다. 예를 들어 :

# Ruby, <s>104</s> <s>101</s> 96 bytes

(언어는 현재 표시되어 있지 않지만 스 니펫은이를 요구하고 구문 분석하며 향후 언어 별 리더 보드를 추가 할 수 있습니다.)

답변:


8

파이썬 2, 179 바이트

from random import*
m=r=input();i=q=r+1;h=[1]+[0]*q*q;exec"h[i]=h[i+~q]+h[i-i%q*q];i+=1;"*r*q
while r:
 x=random()*sum(h[r*q:r*q-~m]);m=0
 while x>0:m+=1;x-=h[r*q+m]
 print m;r-=m

나는 이 Knuth extract의 공식 (39)를 사용 했는데, 이는 n정확히 m부분 을 가진 파티션의 수를 제공합니다 . 이것은의 파티션 수와 같아야하는 일 nm내가 사용하고 해석이다 최대 요소로한다. 파티션의 요소는 최대에서 최소로 생성됩니다. 각 단계에서 공식은 현재의 나머지 n및 최대 허용 요소와 함께 재사용됩니다 .


5

Dyalog APL, 67 59 51 바이트

p←{⍵,⊂1,⍨+/¨⌽⍵↑¨⍨⌽⍳⍴⍵}⍣⎕⊢⍬⋄f←{⍵=0:⍬⋄a,a∇⍵-a←{1++/(?+/⍵)>+\⍵}⍺↑⍵⊃p}⍨ (67 바이트)

p는 summands 로의 p[n][k]파티션 수 또는 동등하게 summand가 가장 큰 파티션 수인 벡터로 구성된 벡터입니다 . 우리는 구축 빈 벡터로 시작하여 , 읽기 ((가) 입력 읽기) 반복적으로 다음을 적용 :nkkpn

{⍵,⊂1,⍨+/¨⌽⍵↑¨⍨⌽⍳⍴⍵}
                 ⍴⍵   ⍝ the current length, initially 0
                ⍳⍴⍵   ⍝ 1 2 ... length
               ⌽⍳⍴⍵   ⍝ length ... 2 1
           ⍵↑¨⍨       ⍝ take length elements from p[1], length-1 from p[2], etc
                      ⍝ padded with 0-s, e.g. if p was (,1)(1 1)(1 1 1)(1 2 1 1)(1 2 2 1 1):
                      ⍝ we get:     (1 0 0 0 0)(1 1 0 0)(1 1 1)(1 2)(,1)
          ⌽           ⍝ reverse it: (,1)(1 2)(1 1 1)(1 1 0 0)(1 0 0 0 0)
       +/¨            ⍝ sum each:   1 3 3 2 1
    1,⍨               ⍝ append 1:   1 3 3 2 1 1
 ⍵,⊂                  ⍝ append the above to the vector of vectors

n응용 프로그램 ( ⍣⎕), 우리는 내장되어 있습니다 p.

f무작위 파티션을 선택합니다. 최대 summands n f k의 임의 파티션입니다 . 입니다 . kf nn f n

{⍵=0:⍬⋄a,a∇⍵-a←{1++/(?+/⍵)>+\⍵}⍺↑⍵⊃p}⍨
                                     ⍨ ⍝ "selfie" -- use n as k if no k is provided
 ⍵=0:⍬                                 ⍝ if n=0 return empty
                                 ⍵⊃p   ⍝ pick the n-th element of p
                               ⍺↑      ⍝ take k elements from that
               {1++/(?+/⍵)>+\⍵}        ⍝ use them as weights to pick a random number 1...k
               {           +\⍵}        ⍝   partial sums of weights
               {    (?+/⍵)    }        ⍝   a random number 1...sum of weights
               {    (?+/⍵)>+\⍵}        ⍝   which partial sums is it greater than?
               {  +/          }        ⍝   count how many "greater than"-s
               {1+            }        ⍝   we're off by one
             a←                        ⍝ this will be the greatest number in our partition
         a∇⍵-a                         ⍝ recur with n1=n-a and k1=a
       a,                              ⍝ prepend a

일부 개선 사항 :

  • p약간 더 나쁘지만 (아직도 충분하지만) 성능이 좋은 인라인

  • p재 배열 계산 1,캐릭터 저장

  • 설정 {1++/(?+/⍵)>+\⍵}함께 기차에 1+앞에 :1+(+/(?+/)>+\)

  • 완전한 프로그램을 얻기 위해 f익명의 함수를 만들고 인수로 평가 (평가 된 입력)

{⍵=0:⍬⋄a,a∇⍵-a←1+(+/(?+/)>+\)⍺↑⍵⊃{⍵,⊂⌽1,+/¨⍵↑¨⍨⌽⍳⍴⍵}⍣⍵⊢⍬}⍨⎕ (59 바이트)

n = 5로 테스트

n = 65로 테스트

다음 링크는 n = 5 천 번 실행되며 각 파티션의 빈도에 대한 통계를 수집합니다. ⎕rl←0 ⋄ {⍺,⍴⍵}⌸ {⍵=0:⍬⋄a,a∇⍵-a←1+(+/(?+/)>+\)⍺↑⍵⊃{⍵,⊂⌽1,+/¨⍵↑¨⍨⌽⍳⍴⍵}⍣⍵⊢⍬}⍨ ¨10000⍴5


Roger Hui의 도움으로 더 많은 개선 사항 :

  • 교체 {⍵=0:A⋄B}와 함께 {×⍵:B⋄A}. Signum ( ×⍵)은에 대해 true를 반환하고에 대해 ⍵>0false를 반환합니다 ⍵=0.

  • 대신 (+/(?+/)>+\)+/b<?⊃⌽b←+\이 문자를 저장,

  • 계산에 벡터 행렬 대신에 벡터를 사용 p: 교체 ⍵⊃{⍵,⊂⌽1,+/¨⍵↑¨⍨⌽⍳⍴⍵}⍣⍵⊢⍬⊃↓(0,⍨⊢⍪⍨1 1⍉+\)⍣⍵⍪1.

{×⍵:a,a∇⍵-a←1++/b<?⊃⌽b←+\⍺↑⊃↓(0,⍨⊢⍪⍨1 1⍉+\)⍣⍵⍪1⋄⍬}⍨ (51 바이트)

테스트 n = 5 ; 테스트 n = 65 ; 주파수 통계


2
Roger Hui는 어떻게 도움을 받습니까?
FUZxxl

5
장난감 APL 통역사를 작성하여 자신과 같은 회사에 고용하십시오. 위의 표현을 도전으로 삼아 그가 취한 모든 인물에 대해 맥주 한 잔을 약속하십시오. 그는 맥주를 마시지 않기 때문에 더 적은 캐릭터와 더 많은 술을 얻을 수 있습니다.
ngn

1
내가 참조. 그것은 깔끔한 전략입니다. 제가 그것을 재현 할 수 있는지 봅시다 ... Dyalog APL이 u/\. y조만간 J와 같은 것을 얻을 수 있는지 물어볼 수 있습니까 ?
FUZxxl


물어봐 줘서 고마워 이제 선형 시간에도 가능한지 궁금합니다.
FUZxxl

4

GolfScript, 90 바이트

~[[[1.]]]\({..[[{{(\{)}%+}%1$,1$,-=}%[1,]@0=+{1+}%]zip{{(\.,/*~}%.,.rand@=+}:^%]\+}*0=^(;`

온라인 데모

이것은 단순히 카운트를 추적하는 대신 카운트와 카운트 된 요소 중 하나를 균일하게 선택하는 내 (간단한) 파티션 계산 코드 의 적응입니다 .

두 가지를 나란히 비교 :

~[[[1.]]]\({..[[{{(\{)}%+}%1$,1$,-=}%[1,]@0=+{1+}%]zip{{(\.,/*~}%.,.rand@=+}:^%]\+}*0=^(;`
 [[ 1  ]]\({..[[{          1$,1$,-=}%  0 @0=+     ]zip{{+}*                }:^%]\+}*0=^

차이점 :

  • 초기 ~는 이것이 스 니펫이 아닌 프로그램이기 때문입니다.
  • [1.]대체 1무엇을의 변화에 대응 추적합니다.
  • 추가 {(\{)}%+}%는 해당 파티션의 각 요소를 증가시키고{1+}% 추가1 시키고 파티션에 합니다.
  • 0 된다 [0] (에 golfed 1,추적 무슨 변화의 일환으로)하지만, 다른 하나 앞에 추가 할 때 배열을 유지해야하기 때문에 그것은 여분의 필요[ ] .
  • 간단한 합계 {+}* 는 파티션에서 가중치를 적용한 선택이되고 개수를 합산합니다.
  • 그만큼 (;` 멋진 형식으로 출력과 풋 파티션에서 카운트를 제거합니다.

테스트 프레임 워크

;7000,{;
  '5'

  ~[[[1.]]]\({..[[{{(\{)}%+}%1$,1$,-=}%[1,]@0=+{1+}%]zip{{(\.,/*~}%.,.rand@=+}:^%]\+}*0=^(;`

}%
:RESULTS
.&${
  RESULTS.[2$]--,' '\n
}/

다른 횟수의 시험을 실행하려면 초기 7000을 조정하십시오. 온라인 데모에서는이 속도가 너무 느립니다.


3

자바, 285 267 바이트

int[][]p;void p(int n){p=new int[n+1][n+1];int a=n,b=k(n,a),c,d;for(b*=Math.random();n>0;System.out.print(c+" "),n-=a=c)for(c=0;c++<(a<n?a:n)&b>=(d=k(n-c,c));b-=d);}int k(int n,int k){if(p[n][k]<1)for(int a=0,b=0;b<k&b++<n;p[n][k]=a)a+=k(n-b,b);return n>0?p[n][k]:1;}

이것은 TheBestOne의 답변과 같은 방법이지만 맵 대신 간단한 배열을 사용합니다. 또한 무작위 파티션을List 콘솔에 인쇄합니다.

아래는 100000 번 실행되는 테스트 프로그램입니다. 예를 들어 n=5, 모든 세트는 마지막 경기에서 완벽한 1/7의 0.64 % 내에있었습니다.

public class Partition {
    public static void main(String[] args) {
        Partition p = new Partition();
        for(int i=0;i<100000;i++){
            p.p(5);
            System.out.println();
        }
    }

    int[][]p;

    void p(int n){
        p=new int[n+1][n+1];
        int a=n,b=k(n,a),c,d;
        for(b*=Math.random();n>0;System.out.print(c+" "),n-=a=c)
            for(c=0;c++<(a<n?a:n)&b>=(d=k(n-c,c));b-=d);
    }

    int k(int n,int k){
        if(p[n][k]<1)
            for(int a=0,b=0;b<k&b++<n;p[n][k]=a)
                a+=k(n-b,b);
        return n>0?p[n][k]:1;
    }

}

3
Math.min전화를 걸 었지만 전화를 (k<n?k:n)완전히 버리고 두 가지 확인 만하면 더 나아갈 수 있습니다 b<k&b++<n. 또한 n>0조건부 루프 의 일부를 쉽게 제거 할 수 있습니다 ( 음이 아닌 것으로 보장 될 때로 n>0&b<n줄 이므로 ). b<nb
피터 테일러

@PeterTaylor 감사합니다. 다시 한 번 살펴보면 여분의 반품 명세서와 별도의 명세서를 제거하겠습니다.int 선언도 .
Geobits

3

CJam, 64 56 바이트

ri_L{_0>{\,f{)_@1$-j+}{)@)2$+:Umr@<@@?U+}*}{!a\;}?}2j);p

이 스크립트를 사용하여 테스트 할 수 있습니다.

ria100*{_L{_0>{\,f{)_@1$-j+}{)@)2$+:Umr@<@@?U+}*}{!a\;}?}2j);}%__|\f{_,\2$a-,-}2/p

설명

ri_                  " Read an integer and duplicate. ";
L{                   " Create a memoized function of the maximum and the sum, which returns
                       a random partition, and the total number of partitions as the last item. ";
    _0>              " If sum > 0: ";
    {
        \,f{         " For I in 0..max-1: ";
            )_@1$-   " Stack: I+1 I+1 sum-I-1 ";
            j+       " Recursively call with the two parameters, and prepend I+1. ";
        }
        {            " Reduce on the results: ";
            )@)2$+   " Stack: partition1 total1 partition2 total1+total2 ";
            :Umr     " U = total1+total2, then generate a random number smaller than that. ";
            @<@@?    " If it is <total1, choose partition1, else choose partition2. ";
            U+       " Append the total back to the array. ";
        }*
    }
    {!a\;}?          " Else return [0] if negative, or [1] if zero. ";
}2j
);p                  " Discard the total and print. ";

2
당신은 당신의 대답의 잘못된 "아주 잘 골프"부분을 제거해야합니다;)
anatolyg

@anatolyg 제거되었습니다. 그러나 여전히 일부 바이트를 제거하는 것이 가능하다고 생각합니다. 나는 너무 게으르다.
jimmy23013

3

Pyth, 64 바이트

용도 a) Pyth가 자동으로 메모리를 메모리에 저장하므로 캐시가 없음, b) 목록에 추가하지 않고 각각을 인쇄하고 c)가 Pyth로 변환된다는 점을 제외하고 /programming//a/2163753/4230423을 .

M?smg-Gddr1hhS,GHG1Akd,QOgQQWQFNr1hhS,QkKg-QNNI<dKB-=dK)N=kN-=QN

시간이있을 때 이것에 대한 설명을 게시 할 것이지만 여기에 해당하는 파이썬 코드가 있습니다.

g=lambda G,H: sum(map(lambda d:g(G-d, d), range(1, (H if H<G else G) + 1))) if G else 1
Q=input()
k,d = Q,random.randrange(g(Q, Q))
while Q:
    for N in range(1, min(k, Q) + 1):
        K = g(Q-N, N)
        if d < K:
            break
        d -= K
    print N
    k=N
    Q -= N

편집 : 마침내 설명을하기 위해 돌아 왔습니다.

M                Lambda g(G,H)
 ?         G     If G truthy
  s              Sum
   m             Map
    g            Recursive call
     -Gdd        G-d,d
    r            Range
     1           1 to
     h           +1
      hS         First element of sorted (does min)
       ,GH       From G and H
   1             Else 1
A                Double assign
 kd              Vars k and d
 ,               To vals
  Q              Q (evaled input)
  O              Randrange 0 till val
   gQQ           Call g(Q, Q)
WQ               While Q is truthy
 FN              For N in
  r              Range
   1             From one
   h             Till +1
    hS,QK        Min(Q,K)
  Kg             K=g(
   -QN           Q-N
   N             N
  I<dK           If d<k
   B             Break (implicit close paren)
  -=dk           Subtracts d-=k
 )               Close out for loop
 N               Prints N
 =kN             Set k=N
 -=QN            Subtracts Q-=N

2

옥타브, 200

function r=c(m)r=[];a=eye(m);a(:,1)=1;for(i=3:m)for(j=2:i-1)a(i,j)=a(i-1,j-1)+a(i-j,j);end;end;p=randi(sum(a(m,:)));while(m>0)b=a(m,:);c=cumsum(b);x=min(find(c>=p));r=[r x];p=p-c(x)+b(x);m=m-x;end;end

언 골프 드 :

function r=c(m)
  r=[];
  a=eye(m);
  a(:,1)=1;
  for(i=3:m)
    for(j=2:i-1)
      a(i,j)=a(i-1,j-1)+a(i-j,j);
    end;
  end;
  p=randi(sum(a(m,:)));
  while(m>0)
    b=a(m,:);
    c=cumsum(b);
    x=min(find(cumsum(b)>=p));
    r=[r x];
    p=p-c(x)+b(x);
    m=m-x;
  end
end

친절하게 인용 된 크 누스 추출물에 따르면 , 각 셀 (m, n) m이 가장 큰 수 의 파티션 수를 반영하는 정사각 행렬 n을 구성하십시오. 예를 들어, 5,2이 두 개의 유효한 파티션이기 때문에 우리 둘 제공 2,2,1하고 2,1,1,1. 6,3우리에게주는 3 3,1,1,1, 3,2,13,3.

이제 p 번째 파티션을 결정적으로 찾을 수 있습니다. 여기서는 p임의의 숫자로 생성 되지만 스크립트를 약간 변경할 수 있으므로 p매개 변수도 있습니다.

function r=c(m,p)
  r=[];
  a=eye(m);
  a(:,1)=1;
  for(i=3:m)
    for(j=2:i-1)
      a(i,j)=a(i-1,j-1)+a(i-j,j);
    end;
  end;
  while(m>0)
    b=a(m,1:m);
    c=cumsum(b);
    x=min(find(c>=p));
    r=[r x];
    p=p-c(x)+b(x);
    m=m-x;
  end
end

이제 각 결과가 p에만 의존한다는 것을 결정적으로 보여줄 수 있습니다.

octave:99> for(i=1:7)
> c(5,i)
> end
ans =

   1   1   1   1   1

ans =

   2   1   1   1

ans =

   2   2   1

ans =

   3   1   1

ans =

   3   2

ans =

   4   1

ans =  5

따라서 p가 무작위로 생성 된 원본으로 돌아가서 각 결과가 동일하게 보장 될 수 있습니다.


당신의 5,2 예제에 대해 잘 모르겠습니다. 두 파티션이 (2,2,1)와 같으면 안됩니다 (2,1,1,1,1)(나열한 두 개의 숫자가보다 큼 2).
Martin Ender 2019

네 말이 맞아, 일이 뒤 틀렸어 두 개의 구성 요소가있는 두 개의 파티션과로 시작하는 두 개의 파티션이 2있습니다. 나는 후자를 의미했다.
dcsohl

2

R, 198 바이트

function(m){r=c();a=diag(m);a[,1]=1;for(i in 3:m)for(j in 2:(i-1))a[i,j]=a[i-1,j-1]+a[i-j,j];p=sample(sum(a[m,]),1);while(m>0){b=a[m,];c=cumsum(b);x=min(which(c>=p));r=c(r,x);p=p-c[x]+b[x];m=m-x};r}

언 골프 드 :

f <- function(m) {
    r <- c()
    a <- diag(m)
    a[, 1] <- 1
    for (i in 3:m)
        for (j in 2:(i-1))
            a[i, j] <- a[i-1, j-1] + a[i-j, j]
    p <- sample(sum(a[m, ]), 1)
    while (m > 0) {
        b <- a[m, ]
        c <- cumsum(b)
        x <- min(which(c >= p))
        r <- c(r, x)
        p <- p - c[x] + b[x]
        m <- m - x
    }
    return(r)
}

옥타브에서 @dcsohl의 훌륭한 솔루션 과 동일한 구조를 따릅니다. 므로 크 누스 추출물을 게시 한 .

R에서 더 창의적인 솔루션을 만들 수 있다면 나중에 이것을 편집 할 것입니다. 그 동안 모든 의견을 환영합니다.


1

자바, 392 바이트

import java.util.*;Map a=new HashMap();List a(int b){List c=new ArrayList();int d=b,e=b(b,d),f=(int)(Math.random()*e),g,i;while(b>0){for(g=0;g++<Math.min(d, b);f-=i){i=b(b-g,g);if(f<i)break;}c.add(g);d=g;b-=g;}return c;}int b(int b,int c){if(b<1)return 1;List d=Arrays.asList(b,c);if(a.containsKey(d))return(int)a.get(d);int e,f;for(e=f=0;f++<Math.min(c, b);)e+=b(b-f,f);a.put(d,e);return e;}

와 전화 a(n) . 반환 ListInteger

들여 쓰기 :

import java.util.*;

Map a=new HashMap();

List a(int b){
    List c=new ArrayList();
    int d=b,e=b(b,d),f=(int)(Math.random()*e),g,i;
    while(b>0){
        for(g=0;g++<Math.min(d, b);f-=i){
            i=b(b-g,g);
            if(f<i)
                break;
        }
        c.add(g);
        d=g;
        b-=g;
    }
    return c;
}

int b(int b,int c){
    if(b<1)
        return 1;
    List d=Arrays.asList(b,c);
    if(a.containsKey(d))
        return(int)a.get(d);
    int e,f;
    for(e=f=0;f++<Math.min(c, b);)
        e+=b(b-f,f);
    a.put(d,e);
    return e;
}

에서 적응 /programming//a/2163753/4230423 하고 골프

작동 방식 : O ( n 2 ) 시간 에 정수 n의 파티션 수를 계산할 수 있습니다 . 부작용으로, 이것은 크기 O ( n 2 ) 의 테이블을 생성하며, 우리는 정수 k에 대해 nk 번째 파티션 을 생성하는 데 사용할 수 있습니다 O ( N을 ) 시간 .

합시다 = 파티션 수를 . 난수 k 를 0에서 1까지 선택하십시오. k 번째 파티션을 생성하십시오 .

평소처럼 제안은 환영합니다 :)


1

파이썬 2, 173 바이트

from random import*
N,M=input__
R=67;d=[(0,[])]*R*R
for k in range(R*R):p,P=d[k+~R];q,Q=d[k-k%R*R];d[k]=p+q+0**k,[[x+1 for x in Q],[1]+P][random()*(p+q)<p]
print d[N*R+M][1]

재귀는 사전을 만든다 d키와 k쌍 대표 (n,m)하여 k=67*n+m((가) 보장을 사용 n<=65). 항목은 파티션 수의 튜플입니다.nm부 및 임의 같은 파티션. 카운트는 재귀 공식에 의해 계산됩니다 (페인에 지적 해 주셔서 감사합니다)

f(n,m) = f(n-1,m-1) + f(n,n-m),

랜덤 분할은 그 수에 비례 한 확률로 두 가지 중 하나를 선택함으로써 업데이트된다. 추가는 업데이트를 수행하여 수행됩니다.1 첫 번째 분기에 를 두 번째 분기마다 모든 요소를 ​​증가 됩니다.

나는 문제의 많은의 범위를 벗어날 값을 받고 있었다 mn제로의 수를 제공 할 수 있습니다. 처음에는 기본적으로 카운트가 0이고 빈 목록이있는 사전을 사용했습니다. 여기서는 목록을 사용하고 대신이 기본 항목으로 채 웁니다. 음수 인덱스를 사용하면 목록을 끝에서 읽게되므로 기본 항목은 끝 부분에 도달하지 못하며 랩 어라운드는 만있는 영역 만 터치합니다 m>n.


1

80386 머신 코드, 105 바이트

코드의 16 진 덤프 :

60 8b fa 81 ec 00 41 00 00 33 c0 8b f4 33 d2 42
89 14 06 42 33 ed 8b d8 03 2c 1e 2a fa 73 f9 83
c6 04 89 2c 06 42 3b d1 76 ea fe c4 3a e1 76 db
33 d2 0f c7 f0 f7 f5 86 e9 85 d2 74 1b 33 c0 8d
34 0c 39 14 86 77 03 40 eb f8 2b 54 86 fc 40 89
07 83 c7 04 2a e8 77 e1 42 89 17 83 c7 04 fe cd
7f f7 4a b6 41 03 e2 61 c3

C 함수로서 : void random_partition(int n, int result[]);. 제공된 버퍼의 숫자 목록으로 결과를 리턴합니다. 그것은 어떤 방식으로도 목록의 끝을 표시하지 않지만 사용자는 숫자를 누적하여 끝을 발견 할 수 있습니다-합계가 같을 때 목록이 끝납니다.n .

사용 방법 (Visual Studio에서) :

#include <stdio.h>

__declspec(naked) void __fastcall random_partiton(int n, int result[])
{
#define a(byte) __asm _emit 0x ## byte
a(60) a(8b) a(fa) a(81) a(ec) a(00) a(41) a(00) a(00) a(33) a(c0) a(8b) a(f4) a(33) a(d2) a(42)
a(89) a(14) a(06) a(42) a(33) a(ed) a(8b) a(d8) a(03) a(2c) a(1e) a(2a) a(fa) a(73) a(f9) a(83)
a(c6) a(04) a(89) a(2c) a(06) a(42) a(3b) a(d1) a(76) a(ea) a(fe) a(c4) a(3a) a(e1) a(76) a(db)
a(33) a(d2) a(0f) a(c7) a(f0) a(f7) a(f5) a(86) a(e9) a(85) a(d2) a(74) a(1b) a(33) a(c0) a(8d)
a(34) a(0c) a(39) a(14) a(86) a(77) a(03) a(40) a(eb) a(f8) a(2b) a(54) a(86) a(fc) a(40) a(89)
a(07) a(83) a(c7) a(04) a(2a) a(e8) a(77) a(e1) a(42) a(89) a(17) a(83) a(c7) a(04) a(fe) a(cd)
a(7f) a(f7) a(4a) a(b6) a(41) a(03) a(e2) a(61) a(c3)
}

void make_stack() // see explanations about stack below
{
    volatile int temp[65 * 64];
    temp[0] = 999;
}

int main()
{
    int result[100], j = 0, n = 64, counter = n;
    make_stack(); // see explanations about stack below

    random_partiton(n, result);

    while (counter > 0)
    {
        printf("%d ", result[j]);
        counter -= result[j];
        ++j;
    }
    putchar('\n');
}

출력 예 (n = 64) :

21 7 4 3 3 3 3 2 2 2 2 1 1 1 1 1 1

이것은 많은 설명이 필요합니다 ...

물론 다른 사람들도 사용했던 알고리즘을 사용했습니다. 복잡성에 대한 요구 사항은 선택하지 않았습니다. 따라서 알고리즘을 너무 많이 설명 할 필요가 없습니다. 어쨌든:

최대 이하의 부분을 사용 f(n, m)하는 n요소 의 분할 수로 표시합니다 m. 두 번째 배열을 C로 선언 된 2D 배열에 저장합니다 f[65][64]. 첫 번째 색인은 n이고 두 번째 색인은m-1 . 나는 지원 n=65이 너무 어려워서 버렸다고 결정했다.

이 테이블을 계산하는 C 코드는 다음과 같습니다.

#define MAX_M 64
int f[(MAX_M + 1) * MAX_M];
int* f2;
int c; // accumulates the numbers needed to calculate f(n, m)
int m;
int k; // f(k, m), for various values of k, are accumulated
int n1;

for (n1 = 0; n1 <= n; ++n1)
{
    f2 = f;
    f2[n1 * MAX_M] = 1;
    for (m = 2; m <= n; ++m)
    {
        c = 0;
        k = n1;
        while (k >= 0)
        {
            c += f2[k * MAX_M];
            k -= m;
        }
        ++f2;
        f2[n1 * MAX_M] = c;
    }
}

이 코드는 난독 화 된 스타일을 가지므로 어셈블리 언어로 쉽게 변환 할 수 있습니다. 최대 요소를 계산하는데 f(n, n), 이는 n요소 의 분할 수입니다 . 이 코드가 완료되면 임시 변수 c에 필요한 숫자가 포함되며 이는 임의의 파티션을 선택하는 데 사용할 수 있습니다.

int index = rand() % c;

나중에 index생성 된 테이블을 사용하여 필요한 형식 (숫자 목록)으로 변환됩니다.

do {
    if (index == 0)
        break;

    m = 0;
    f2 = &f[n * MAX_M];
    while (f2[m] <= index)
    {
        ++m;
    }

    index -= f2[m-1];
    ++m;
    *result++ = m;
    n -= m;
} while (n > 0);

do {
    *result++ = 1;
    --n;
} while (n > 0);

이 코드는 어셈블리 언어로의 변환에 최적화되어 있습니다. 작은 "버그"가 있습니다 : 파티셔닝에 포함되어 있지 않은 경우1 에 끝에 숫자 마지막 루프에서 발생 n = 0하고 불필요한 1요소를 출력합니다 . 그러나 인쇄 코드는 숫자의 합계를 추적하고이 불필요한 숫자를 인쇄하지 않기 때문에 아프지 않습니다.

인라인 어셈블리로 변환하면이 코드는 다음과 같습니다.

__declspec(naked) void _fastcall random_partition_asm(int n, int result[])
{
    _asm {
        pushad;

        // ecx = n
        // edx = m
        // bh = k; ebx = k * MAX_M * sizeof(int)
        // ah = n1; eax = n1 * MAX_M * sizeof(int)
        // esp = f
        // ebp = c
        // esi = f2
        // edi = result

        mov edi, edx;
        sub esp, (MAX_M + 1) * MAX_M * 4; // allocate space for table
        xor eax, eax;
    row_loop:
        mov esi, esp;
        xor edx, edx;
        inc edx;
        mov dword ptr [esi + eax], edx;
        inc edx;

    col_loop:
        xor ebp, ebp;
        mov ebx, eax;

    sum_loop:
        add ebp, [esi + ebx];
        sub bh, dl;
        jae sum_loop;

        add esi, 4;
        mov [esi + eax], ebp;
        inc edx;
        cmp edx, ecx;
        jbe col_loop;

        inc ah;
        cmp ah, cl;
        jbe row_loop;

        // Done calculating the table

        // ch = n; ecx = n * MAX_M * sizeof(int)
        // eax = m
        // ebx = 
        // edx = index
        // esp = f
        // esi = f2
        // ebp = c
        // edi = result

        xor edx, edx;
        rdrand eax; // generate a random number
        div ebp; // generate a random index in the needed range
        xchg ch, cl; // multiply by 256

    n_loop:
        test edx, edx;
        jz out_trailing;
        xor eax, eax;
        lea esi, [esp + ecx];

    m_loop:
        cmp [esi + eax * 4], edx;
        ja m_loop_done;
        inc eax;
        jmp m_loop;
    m_loop_done:

        sub edx, [esi + eax * 4 - 4];
        inc eax;
        mov [edi], eax;
        add edi, 4;
        sub ch, al;
        ja n_loop;

    out_trailing:
        inc edx;
    out_trailing_loop:
        mov dword ptr [edi], edx;
        add edi, 4;
        dec ch;
        jg out_trailing_loop;

        dec edx;
        mov dh, (MAX_M + 1) * MAX_M * 4 / 256;
        add esp, edx;
        popad;
        ret;
    }
}

몇 가지 재미있는 점 :

  • 난수를 생성하는 데 3 바이트의 머신 코드 만 있으면됩니다 (rdrand 명령)
  • 일치하게, 테이블의 크기는 64이므로 한 행의 크기는 256 바이트입니다. 나는 이것을 사용하여 "하이 바이트"레지스터에서 행 인덱스를 유지한다ah 하여 256 자동 곱셈을 제공하는 이를 활용하기 위해 지원을 희생했습니다.n = 65 . 이 죄를 용서받을 수 있기를 바랍니다 ...
  • 스택의 공간 할당은 스택 포인터 레지스터에서 0x4100을 빼서 수행됩니다 esp. 이것은 6 바이트 명령입니다! 이 숫자를 다시 추가 할 때 5 바이트로 처리했습니다.

        dec edx; // here edx = 1 from earlier calculations
        mov dh, (MAX_M + 1) * MAX_M * 4 / 256; // now edx = 0x4100
        add esp, edx; // this deallocates space on stack
    
  • MS Visual Studio 에서이 기능을 디버깅 할 때 스택에 할당 된 공간에 데이터를 쓸 때 충돌한다는 것을 알았습니다! 약간의 파고 후에, 나는 일종의 스택 오버런 보호를 발견했다 : OS는 스택에 대해 매우 제한된 범위의 가상 주소만을 할당하는 것처럼 보인다; 함수가 주소에 너무 멀리 접근하면 OS는 오버런이라고 가정하고 프로그램을 종료시킵니다. 그러나 함수에 로컬 변수가 많은 경우 OS는 추가 "매직"을 수행하여 작동합니다. 따라서 스택에 큰 배열이 할당 된 빈 함수를 호출해야합니다. 이 함수가 반환되면 추가 스택 VM 페이지가 할당되어 사용할 수 있습니다.

        void make_stack()
        {
            volatile int temp[65 * 64];
            temp[0] = 999; // have to "use" the array to prevent optimizing it out
        }
    
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.