Tấm giấy phép hoàn hảo


33

Tấm giấy phép hoàn hảo

Bắt đầu từ vài năm trước, tôi đã tạo cho mình một trò chơi nhỏ khi lái xe xung quanh: kiểm tra xem biển số xe gần đó có "hoàn hảo" không. Nó tương đối hiếm, nhưng thú vị khi bạn tìm thấy.

Để kiểm tra xem một tấm giấy phép là hoàn hảo:

  1. Tổng hợp các ký tự, với A = 1, B = 2, ... Z = 26.
  2. Lấy từng đoạn số liên tiếp và tính tổng chúng; nhân từng khoản tiền với nhau.

Nếu các giá trị trong phần 1 và phần 2 bằng nhau, xin chúc mừng! Bạn đã tìm thấy một tấm giấy phép hoàn hảo!

Ví dụ

License plate: AB3C4F

Digits -> 3 * 4 
        = 12
Chars  -> A + B + C + F 
        = 1 + 2 + 3 + 6 
        = 12
12 == 12 -> perfect!


License plate: G34Z7T

Digits -> (3 + 4) * 7 
        = 49
Chars  -> G + Z + T 
        = 7 + 26 + 20 
        = 53
49 != 53 -> not perfect!


License plate: 10G61

Digits -> (1 + 0) * (6 + 1)
        = 7
Chars  -> G
        = 7
7 == 7 -> perfect!

Các thách thức

Tôi đã sử dụng biển số xe có chiều dài 5 và 6 làm ví dụ, nhưng quy trình này hợp lệ cho bất kỳ chiều dài tấm nào. Thử thách của bạn là, với độ dài N cho trước, trả về số biển số xe hoàn hảo có độ dài đó. Đối với các mục đích của thử thách, biển số xe hợp lệ là bất kỳ sự kết hợp nào giữa các chữ số 0-9 và ký tự AZ. Các tấm phải chứa cả một ký tự và một chữ số để được coi là có khả năng hoàn hảo. Để kiểm tra mục đích, đây là các giá trị tôi nhận được (mặc dù tôi không thể 100% về tính đúng đắn của chúng, hahaha)

N < 2: 0
N = 2: 18
N = 3: 355
N = 4: 8012 

Ghi chú

Nếu bằng cách nào đó nó làm cho vấn đề trở nên đơn giản hơn trong ngôn ngữ của bạn, bạn có thể xuất tỷ lệ các biển số xe hoàn hảo cho một N nhất định, thành ít nhất 2 chữ số có nghĩa.

N < 2: 0
N = 2: 0.0138888...
N = 3: 0.0076088...
N = 4: 0.0047701...

HOẶC, bạn có thể xuất mod giá trị tương đương 256

N < 2: 0
N = 2: 18
N = 3: 99
N = 4: 76

Chiến thắng ngắn nhất!


2
Chào mừng đến với trang web! Tôi nghĩ rằng đây là một thử thách tốt, nhưng các kết quả đầu ra được phép bổ sung khiến cho việc ghi câu trả lời thực sự khó khăn. PPCG tìm kiếm các tiêu chí chiến thắng khách quan và thật khó để làm điều đó khi có quá nhiều quyền tự do; điều này không chỉ thay đổi định dạng đầu ra, điều này thực sự thay đổi những gì bạn được phép xuất. Tôi sẽ khuyên bạn nên chỉnh sửa các tùy chọn khác và chỉ yêu cầu xuất ra số biển số xe hoàn hảo cho N.
HyperNeutrino

11
Cá nhân tôi sẽ thích thử thách này hơn rất nhiều nếu chỉ đơn giản là xác nhận xem một tấm giấy phép nhất định có hoàn hảo hay không (đặc biệt là vì bạn không có số chính xác cho các trường hợp thử nghiệm. đang giảm. Không chắc chắn về cảm giác của người khác. Mặc dù vậy, ý tưởng tuyệt vời!
MildlyMilquetoast

4
Tôi đồng ý với Mistah Figgins; Tôi cảm thấy như thế này là về việc tìm kiếm một mô hình, đây vẫn là một thách thức thú vị, nhưng tôi nghĩ nó có thể thu hút nhiều câu trả lời hơn nếu đó chỉ là kiểm tra xác nhận. Mặc dù thử thách rất hay!
HyperNeutrino

1
Tôi đã đăng một thử thách liên quan chặt chẽ , hy vọng rằng nó sẽ thu hút sự chú ý về câu hỏi tuyệt vời này, cũng như đơn giản hóa điều này một chút, chỉ kiểm tra (gần) biển số xe hoàn hảo.
Ông Xcoder

1
@carusocomputing Tôi đã cố gắng hết sức nhưng lại trống rỗng. Tôi đã gửi nó cho giáo viên toán của tôi và anh ta trống rỗng cho đến nay
Christopher

Câu trả lời:


9

Python 3.6, 150 byte

f=lambda n,t=-1,p=-1,a=0:sum(f(n-1,*((t,c+p*(p>=0),a),((t<0 or t)*(p<0 or p),-1,a-c))[c<0])for c in range(-26,10))if n else 0<(t<0 or t)*(p<0 or p)==a

các kết quả:

f(2) = 18
f(3) = 355
f(4) = 8012
f(5) = 218153

Phiên bản Ungolfed với lời giải thích:

digits=[*range(10)]
letters=[*range(1,27)]

def f(n, dt=-1, dp=-1, lt=0):
    if n:
        for d in digits:
            yield from f(n - 1,
                         dt,
                         d if dp < 0 else dp + d,
                         lt
                         )

        for l in letters:
            yield from f(n - 1,
                         dp if dt < 0 else dt if dp < 0 else dt*dp,
                         -1,
                         lt + l
                         )
    else:
        yield 0 < lt == (dt<0 or dt)*(dp<0 or dp)

Vấn đề tập trung vào việc tìm kiếm một cây trong đó mỗi cấp độ của cây tương ứng với một vị trí trong số biển số xe và mỗi nút có 36 con (10 chữ số và 26 chữ cái). Hàm thực hiện tìm kiếm đệ quy của cây, tích lũy các giá trị cho các chữ số và chữ cái khi nó đi.

n is the number of levels to search. 
dp accumulates the sum of a group of digits.
dt accumulates the product of the digit sums.
lt accumulates the sum of the letter values.

For dp and dt, a value < 0 indicates it is not initialized.

Bao gồm golf, chuyển đổi các vòng lặp thành tổng của máy phát điện:

sum(f(n-1, 
      dt,
      d if dp < 0 else dp + d,
      lt) for d in digits)
+
sum(f(n-1,
      dp if dt<0 else dt if dp<0 else dt*dp,
      -1,
      lt+l) for l in letters)

Sau đó kết hợp các máy phát điện. Mã hóa các chữ cái, từ A đến Z, từ -1 đến -26 và các chữ số từ 0 đến 9. Vì vậy, tổng trở thành:

sum(f(n-1, *args) for c in range(-26, 10)),

trong đó args là:

((dp if dt<0 else dt if dp<0 else dt*dp, -1, lt-l) if c <0 else
 (dt, d if dp<0 else dp+d, lt))

Phần còn lại của việc chơi gôn là chuyển đổi chức năng thành lambda, rút ​​ngắn tên biến và đơn giản hóa các biểu thức.


Đây là một giải pháp hùng hồn, thời gian chạy sẽ là gì? n*n*log(n)hoặc một cái gì đó tương tự?
Bạch tuộc ma thuật Urn

@carusocomputing Cảm ơn. Giải pháp vẫn tạo ra tất cả các hoán vị có thể có của độ dài cho trước, do đó, nó có độ phức tạp tương tự như các giải pháp khác. Một cái gì đó như k ** n, trong đó k là số ký hiệu trong bảng chữ cái (ví dụ: 10 chữ số + 26 chữ cái = 36) và n là số ký hiệu trên biển số xe. Chạy nó cho n = 5 yêu cầu kiểm tra 36 ^ 5 = 60,466,176 hoán vị và mất một hoặc hai phút (việc ghi nhớ có thể tăng tốc, nhưng sẽ tốn rất nhiều byte ;-)).
RootTwo

6

APL Dyalog, 57 56 byte

+/(+/0⌈a-9)=×/c*⍨-2-/0,⌈\(+\a×b)×c←2>/0,⍨b←9≥a←↑1↓,⍳⎕⍴36

(giả định ⎕io←0)

ama trận của tất cả các biển số xe hợp lệ (trừ 00...0) được mã hóa với: 0-9 cho các chữ số, 10-35 cho các chữ cái

b bitmask cho nơi xảy ra chữ số

c bitmask cho chữ số cuối cùng trong mỗi nhóm chữ số liên tiếp


Dùng thử trực tuyến cho 1-4 cần thêm bộ nhớ cho 4, nhưng cũng có nhiều cách khác!
Adám

4

Python 2, 359 295 byte

Khá dài; đây là giải pháp tầm thường Tôi tự tin điều này là chính xác, mặc dù nó không phù hợp với các trường hợp thử nghiệm trong thử thách. Các giải pháp tôi nhận được phù hợp với câu trả lời của Dada.

import itertools as i,re as r,string as s
print len([''.join(x)for x in i.product(s.lowercase+s.digits,repeat=input())if(lambda t:r.search('\\D',t)and r.search('\\d',t)and reduce(int.__mul__,[sum(map(int,k))for k in r.split('\\D+',t)if k])==sum([k-96 for k in map(ord,t) if k>96]))(''.join(x))])

-64 byte nhờ các đề xuất từ ​​@numbermaniac


1
Bạn có thể lưu khoảng ba byte trong c (x) và dòng cuối cùng; xóa khoảng cách giữa 96 và for; giữa map(ord,x)if; và trong dòng cuối cùng, giữa .join(x)for. Tôi nghĩ bạn cũng có thể tiết kiệm nhiều hơn nếu bạn xác định lại các chức năng cho lambdas.
numbermaniac

@numbermaniac Cảm ơn! (Tổng cộng 64 byte)
HyperNeutrino

4

Python 2 , 291 287 276 273 byte

lambda n:sum(1for x in s.product(y+z,repeat=n)if(lambda p,s=set:reduce(int.__mul__,[sum(map(int,i))for i in re.findall(r"\d+",p)],1)==sum(ord(i)-64for i in p if ord(i)>64)and s(p)&s(y)and s(p)&s(z))(''.join(x)))
import re,itertools as s,string as t
y=t.uppercase
z=t.digits

Hãy thử trực tuyến!


Các kết quả:

0 0
1 0
2 18
3 355
4 8012

3

Perl 5 , 117 byte

116 byte mã + -pcờ.

$"=",";@F=(A..Z,0..9);map{$r=1;$r*=eval s/./+$&/gr for/\d+/g;$r+=64-ord for/\pl/g;$\+=!$r*/\pl/*/\d/}glob"{@F}"x$_}{

Hãy thử trực tuyến!

Nó cảm thấy khá tối ưu, nhưng tôi không có ý tưởng ngay bây giờ.
Bản thân mã này rất không hiệu quả vì nó tính toán mọi hoán vị về a..z,0..9độ dài n(mất khoảng 1 giây trong n=3, ~ 15 giây cho n=4và ~ 7 phút cho n=5).
Thuật toán khá đơn giản: với mọi tấm kích thước có thể n(được tạo bằng glob"{@F}"x$_- globtoán tử khá kỳ diệu), $r*=eval s/./+$&/gr for/\d+/g;tính toán tích của mỗi khối chữ số và $r+=64-ord for/\pl/gtrừ đi trọng số của các chữ cái. Sau đó, chúng tôi tăng bộ đếm $\nếu $r0( !$r) và nếu tấm chứa số và chữ ( /\pl/*/\d/). $\được in ngầm ở cuối nhờ -pcờ.

Lưu ý rằng các số liệu mà tôi có được là n=2 -> 18, n=3 -> 355, n=4 -> 8012, n=5 -> 218153. Tôi khá chắc chắn đây là những câu đúng, nhưng tôi có thể nhầm, trong trường hợp đó hãy cho tôi biết và tôi sẽ xóa câu trả lời này.


3

APL (Dyalog) , 71 byte

Toàn thân chương trình. Lời nhắc cho N. N≥4 đòi hỏi số lượng lớn bộ nhớ và tính toán.

+/((+/⊢⍳∩)∘⎕A=(×/'\d+'S{+/⍎¨⍵.Match}))¨l/⍨∧⌿∨/¨c∘.∊l←,(∊c←⎕DA)∘.,⍣⎕⊂⍬

Hãy thử trực tuyến!


2

Scala, 265 byte

(n:Int)=>{val i=('A'to'Z')++('0'to'9');Seq.fill(n)(i).flatten.combinations(n).flatMap(_.permutations).map(_.mkString).count(l=>"(?=.*[A-Z])(?=.*\\d)".r.findAllIn(l).size>0&&l.map(_-64).filter(_>0).sum==l.split("[A-Z]").filter(""<).map(_.map(_-48).sum).reduce(_*_))}

Giải thích:

(n:Int) => {
    val i = ('A' to 'Z') ++ ('0' to '9');                       // All license plates available characters.
    Seq.fill(n)(i).flatten                                      // Simulate combination with repetition (each character is present n times)
        .combinations(n)                                        // and generate all combinations of size n (all license plates).
        .flatMap(_.permutations)                                // For each combination, generate all permutations (ex. : Seq('A', '1') => Seq('A', '1') and Seq('1', 'A')), and
        .map(_.mkString)                                        // convert each permutation to String (Seq('A', '1') => "A1").
        .count ( l =>                                           // Then count all strings having :
            "(?=.*[A-Z])(?=.*\\d)".r.findAllIn(l).size > 0 &&   // at least 1 character and 1 digit and
            l.map(_ - 64).filter(_ > 0).sum ==                  // a sum of characters (> 'A' or > 64) equals to
            l.split("[A-Z]").filter(""<)
                .map(_.map(_ - 48).sum)
                .reduce(_*_)                                    // the product of sum of digits groups (split String by letters to get digits groups)
        )
}

Ghi chú:

  • -64-48được sử dụng để chuyển đổi một Char(tương ứng chữ cái và chữ số) để nó Intgiá trị ( 'A' - 64 = 1, 'B' - 64 = 2, ..., '9' - 48 = 9)
  • Bộ lọc trong l.split("[A-Z]").filter(""<)được sử dụng để loại bỏ ""các giá trị nếu lbắt đầu bằng một chữ cái (ví dụ "A1".split("[A-Z]") => Array("", 1):). Có thể có một giải pháp tốt hơn và ngắn hơn

Các trường hợp thử nghiệm:

val f = (n:Int) => ...  // assign function
(1 to 5).foreach ( i =>
    println(s"N = $i: ${f(i)}")
)

Các kết quả :

N = 1: 0
N = 2: 18
N = 3: 355
N = 4: 8012
N = 5: 218153

Hàm này khá chậm n > 4vì tất cả các kết hợp phải được tạo.


2

Java, 382 365 byte

  • Đã lưu 17 byte, nhờ Kevin Cruijssen

Chơi gôn

int h(String s){int m=0;for(int c:s.toCharArray())m+=c-48;return m;}
int g(String t){int d=1,c=0;for(String s:t.split("[^0-9]"))d*=h(s);for(String s:t.split("[^A-Z]"))c+=s.charAt(0)-65;return d==c?1:0;}
int f(String t,int n){int m=0;if(t.length()==n)return g(t);for(int d=48;d<58;)m+=f(t+d++,n);for(int c=65;c<91;)m+=f(t+c++,n);return m;}
int s(int n){return f("",n);}

Chi tiết

// return sum of adjecent digits
int h(String s)
{
    int sum = 0;
    for(char c : s.toCharArray()) sum += c-'0';
    return sum;
}

// check if perfect
int g(String t)
{
    int d = 1;
    int c = 0;

    for(String s : t.split("[^0-9]")) d *= h(s);
    for(String s : t.split("[^A-Z]")) c += s.charAt(0)-'A';

    return d == c ? 1 : 0;
}

// tree of enumerations
int f(String t, int n)
{
    // base case
    if(t.length() == n)
    {
        return g(t);
    }

    // enumeration
    int sum = 0;
    for(char d='0'; d<='9'; d++) sum += f(t+d, n);
    for(char c='A'; c<='Z'; c++) sum += f(t+c, n);

    return sum;
}

int s(int n){ return f("",n); }

Tôi nghĩ bạn cần một chức năng chỉ lấy nlàm đầu vào.
Christian Sievers

@ChristianSievers đã sửa
Khaled.K

1
Một số điều cần đánh gôn cho mã hiện tại của bạn: int h(String s){int m=0;for(int c:s.toCharArray())m+=c-48;return m;}int g(String t){int d=1,c=0;for(String s:t.split("[^0-9]"))d*=h(s);for(String s:t.split("[^A-Z]"))c+=s.charAt(0)-65;return d==c?1:0;}int f(String t,int n){int m=0;if(t.length()==n)return g(t);for(int d=48;d<58;)m+=f(t+d++,n);for(int c=65;c<91;)m+=f(t+c++,n);return m;}int s(int n){return f("",n);}( 365 byte ) Bạn có thể so sánh phiên bản hiện tại của mình với phiên bản này để xem những thay đổi tôi đã làm (quá nhiều để phù hợp với phần còn lại của nhận xét này). :)
Kevin Cruijssen

@KevinCruijssen thx, giảm 17 byte ngay bây giờ
Khaled.K

2

GAP , 416 byte

Không giành chiến thắng về kích thước mã và cách xa thời gian liên tục, nhưng sử dụng toán học để tăng tốc rất nhiều!

x:=X(Integers);
z:=CoefficientsOfUnivariatePolynomial;
s:=Size;

f:=function(n)
 local r,c,p,d,l,u,t;
 t:=0;
 for r in [1..Int((n+1)/2)] do
  for c in [r..n-r+1] do
   l:=z(Sum([1..26],i->x^i)^(n-c));
   for p in Partitions(c,r) do
    d:=x;
    for u in List(p,k->z(Sum([0..9],i->x^i)^k)) do
     d:=Sum([2..s(u)],i->u[i]*Value(d,x^(i-1))mod x^s(l));
    od;
    d:=z(d);
    t:=t+Binomial(n-c+1,r)*NrArrangements(p,r)*
         Sum([2..s(d)],i->d[i]*l[i]);
   od;
  od;
 od;
 return t;
end;

Để vắt kiệt khoảng trắng không cần thiết và lấy một dòng có 416 byte, hãy chuyển qua đây:

sed -e 's/^ *//' -e 's/in \[/in[/' -e 's/ do/do /' | tr -d \\n

Máy tính xách tay "được thiết kế cho Windows XP" cũ của tôi có thể tính toán f(10)trong vòng chưa đầy một phút và tiến xa hơn trong vòng một giờ:

gap> for i in [2..15] do Print(i,": ",f(i),"\n");od;
2: 18
3: 355
4: 8012
5: 218153
6: 6580075
7: 203255386
8: 6264526999
9: 194290723825
10: 6116413503390
11: 194934846864269
12: 6243848646446924
13: 199935073535438637
14: 6388304296115023687
15: 203727592114009839797

Làm thế nào nó hoạt động

Giả sử rằng trước tiên chúng ta chỉ muốn biết số lượng biển số xe hoàn hảo phù hợp với mẫu LDDLLDL, trong đó Lbiểu thị một chữ cái và Dbiểu thị một chữ số. Giả sử chúng ta có một danh sách lcác số sao l[i]cho số cách mà các chữ cái có thể đưa ra giá trị ivà một danh sách tương tự dcho các giá trị chúng ta nhận được từ các chữ số. Sau đó, số lượng biển số xe hoàn hảo với giá trị chung ichỉ là l[i]*d[i], và chúng tôi có được số lượng tất cả các biển số xe hoàn hảo với mẫu của chúng tôi bằng cách tổng hợp tất cả i. Hãy biểu thị hoạt động của số tiền này bằng cách l@d.

Bây giờ ngay cả khi cách tốt nhất để có được các danh sách này là thử tất cả các kết hợp và đếm, chúng ta có thể làm điều này một cách độc lập cho các chữ cái và chữ số, xem xét 26^4+10^3các trường hợp thay vì các 26^4*10^3 trường hợp khi chúng ta chạy qua tất cả các tấm phù hợp với mẫu. Nhưng chúng ta có thể làm tốt hơn nhiều: lchỉ là danh sách các hệ số (x+x^2+...+x^26)^knơi klà số chữ cái, ở đây 4.

Tương tự, chúng ta có được số cách để có được tổng các chữ số trong một kchữ số là các hệ số của (1+x+...+x^9)^k. Nếu có nhiều hơn một chữ số, chúng ta cần kết hợp các danh sách tương ứng với một thao tác d1#d2tại vị trí icó giá trị bằng tổng của tất cả các d1[i1]*d2[i2]vị trí . Cùng với thực tế là nó là song tuyến tính, điều này mang lại một cách hay (nhưng không hiệu quả lắm) để tính toán nó.i1*i2=i . Đây là tích chập Dirichlet, chỉ là sản phẩm nếu chúng ta giải thích các danh sách là hệ số của chuỗi Dirchlet. Nhưng chúng tôi đã sử dụng chúng như các đa thức (chuỗi lũy thừa hữu hạn), và không có cách nào tốt để diễn giải hoạt động cho chúng. Tôi nghĩ rằng sự không phù hợp này là một phần của những gì làm cho nó khó tìm thấy một công thức đơn giản. Chúng ta hãy sử dụng nó trên đa thức và sử dụng cùng một ký hiệu #. Thật dễ dàng để tính toán khi một toán hạng là một đơn thức: chúng ta cóp(x) # x^k = p(x^k)

Lưu ý rằng kcác chữ cái cho giá trị tối đa 26k, trong khi các k chữ số đơn có thể cho giá trị là 9^k. Vì vậy, chúng ta sẽ thường có được sức mạnh cao không cần thiết trong dđa thức. Để loại bỏ chúng, chúng ta có thể tính toán modulo x^(maxlettervalue+1). Điều này mang lại tốc độ lớn và mặc dù tôi đã không chú ý ngay lập tức, thậm chí còn giúp chơi gôn, bởi vì bây giờ chúng ta biết rằng mức độ dkhông lớn hơn mức củal , điều đó giúp đơn giản hóa giới hạn trên trong trận chung kết Sum. Chúng tôi thậm chí còn tăng tốc tốt hơn bằng cách thực hiện một modphép tính trong đối số đầu tiên của Value (xem bình luận) và thực hiện toàn bộ #tính toán ở mức thấp hơn giúp tăng tốc đáng kinh ngạc. Nhưng chúng tôi vẫn đang cố gắng trở thành một câu trả lời chính đáng cho vấn đề chơi gôn.

Vì vậy, chúng tôi đã có của chúng tôi ldvà có thể sử dụng chúng để tính toán số lượng tấm giấy phép hoàn hảo với mẫuLDDLLDL . Đó là con số tương tự như đối với mẫu LDLLDDL. Nói chung, chúng ta có thể thay đổi thứ tự các chữ số có độ dài khác nhau tùy thích, NrArrangementsđưa ra số lượng khả năng. Và trong khi phải có một chữ cái giữa các chữ số, các chữ cái khác không được sửa. Tính Binomialnhững khả năng này.

Bây giờ nó vẫn còn để chạy qua tất cả các cách có thể có độ dài của chữ số chạy. rchạy qua tất cả số lần chạy, cqua tất cả tổng số chữ số vàp qua tất cả các phân vùng ccó triệu hồi r.

Tổng số phân vùng chúng tôi xem xét ít hơn hai phân vùng n+1 và các chức năng phân vùng phát triển như thế nào exp(sqrt(n)). Vì vậy, mặc dù vẫn có những cách dễ dàng để cải thiện thời gian chạy bằng cách sử dụng lại kết quả (chạy qua các phân vùng theo thứ tự khác nhau), để cải thiện cơ bản, chúng ta cần tránh nhìn vào từng phân vùng một cách riêng biệt.

Tính toán nhanh

Lưu ý rằng (p+q)@r = p@r + q@r. Tự nó, điều này chỉ giúp tránh một số phép nhân. Nhưng cùng với (p+q)#r = p#r + q#rnó có nghĩa là chúng ta có thể kết hợp bằng các đa thức bổ sung đơn giản tương ứng với các phân vùng khác nhau. Chúng ta không thể thêm tất cả chúng, bởi vì chúng ta vẫn cần biết lchúng ta phải làm gì với@ kết hợp với yếu tố nào, yếu tố nào chúng ta phải sử dụng và yếu #tố nào vẫn có thể xảy ra.

Chúng ta hãy kết hợp tất cả các đa thức tương ứng với các phân vùng có cùng tổng và độ dài và đã tính đến nhiều cách phân phối độ dài của các chữ số. Khác với những gì tôi suy đoán trong các nhận xét, tôi không cần quan tâm đến giá trị được sử dụng nhỏ nhất hoặc mức độ thường xuyên sử dụng, nếu tôi chắc chắn rằng mình sẽ không gia hạn với giá trị đó.

Đây là mã C ++ của tôi:

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

using bignum = mpz_class;
using poly = std::vector<bignum>;

poly mult(const poly &a, const poly &b){
  poly res ( a.size()+b.size()-1 );
  for(int i=0; i<a.size(); ++i)
    for(int j=0; j<b.size(); ++j)
      res[i+j]+=a[i]*b[j];
  return res;
}

poly extend(const poly &d, const poly &e, int ml, poly &a, int l, int m){
  poly res ( 26*ml+1 );
  for(int i=1; i<std::min<int>(1+26*ml,e.size()); ++i)
    for(int j=1; j<std::min<int>(1+26*ml/i,d.size()); ++j)
      res[i*j] += e[i]*d[j];
  for(int i=1; i<res.size(); ++i)
    res[i]=res[i]*l/m;
  if(a.empty())
    a = poly { res };
  else
    for(int i=1; i<a.size(); ++i)
      a[i]+=res[i];
  return res;
}

bignum f(int n){
  std::vector<poly> dp;
  poly digits (10,1);
  poly dd { 1 };
  dp.push_back( dd );
  for(int i=1; i<n; ++i){
    dd=mult(dd,digits);
    int l=1+26*(n-i);
    if(dd.size()>l)
      dd.resize(l);
    dp.push_back(dd);
  }

  std::vector<std::vector<poly>> a;
  a.reserve(n);

  a.push_back( std::vector<poly> { poly { 0, 1 } } );
  for(int i=1; i<n; ++i)
    a.push_back( std::vector<poly> (1+std::min(i,n+i-i)));
  for(int m=n-1; m>0; --m){
    //    std::cout << "m=" << m << "\n";
    for(int sum=n-m; sum>=0; --sum)
      for(int len=0; len<=std::min(sum,n+1-sum); ++len){
        poly d {a[sum][len]} ;
        if(!d.empty())
          for(int sumn=sum+m, lenn=len+1, e=1;
              sumn+lenn-1<=n;
              sumn+=m, ++lenn, ++e)
            d=extend(d,dp[m],n-sumn,a[sumn][lenn],lenn,e);
      }
  }
  poly let (27,1);
  let[0]=0;
  poly lp { 1 };
  bignum t { 0 };
  for(int sum=n-1; sum>0; --sum){
    lp=mult(lp,let);
    for(int len=1; len<=std::min(sum,n+1-sum); ++len){
      poly &a0 = a[sum][len];
      bignum s {0};
      for(int i=1; i<std::min(a0.size(),lp.size()); ++i)
        s+=a0[i]*lp[i];
      bignum bin;
      mpz_bin_uiui( bin.get_mpz_t(), n-sum+1, len );
      t+=bin*s;
    }
  }
  return t;
}

int main(){
  int n;
  std::cin >> n;
  std::cout << f(n) << "\n" ;
}

Cái này sử dụng thư viện GNU MP. Trên debian, cài đặt libgmp-dev. Biên dịch với g++ -std=c++11 -O3 -o pl pl.cpp -lgmp -lgmpxx. Chương trình lấy lý lẽ của nó từ stdin. Để tính thời gian, sử dụng echo 100 | time ./pl.

Cuối cùng, a[sum][length][i]đưa ra số cách mà các sum chữ số trong các lengthlần chạy có thể đưa ra số i. Trong quá trình tính toán, ở đầu mvòng lặp, nó đưa ra số cách có thể được thực hiện với số lớn hơn m. Tất cả bắt đầu với a[0][0][1]=1. Lưu ý rằng đây là một siêu số của các số chúng ta cần để tính hàm cho các giá trị nhỏ hơn. Vì vậy, gần như cùng một lúc, chúng ta có thể tính toán tất cả các giá trị lên đến n.

Không có đệ quy, vì vậy chúng tôi có một số vòng lặp lồng nhau cố định. (Mức lồng sâu nhất là 6.) Mỗi ​​vòng lặp đi qua một số giá trị là tuyến tính trong ntrường hợp xấu nhất. Vì vậy, chúng ta chỉ cần thời gian đa thức. Nếu chúng ta nhìn sâu hơn về các lồng nhau ijvòng trong extend, chúng tôi thấy một giới hạn trên cho jcác hình thức N/i. Điều đó chỉ nên đưa ra một yếu tố logarit cho jvòng lặp. Vòng lặp trong cùng trong f (với sumnvv) là tương tự. Cũng nên nhớ rằng chúng tôi tính toán với những con số tăng nhanh.

Cũng lưu ý rằng chúng tôi lưu trữ O(n^3)những con số này.

Thực nghiệm, tôi nhận được các kết quả này trên phần cứng hợp lý (i5-4590S): f(50)cần một giây và 23 MB, f(100)cần 21 giây và 166 MB, f(200)cần 10 phút và 1,5 GB, và f(300)cần một giờ và 5,6 GB. Điều này cho thấy một sự phức tạp thời gian tốt hơn O(n^5).


Vì đây là một thử thách golf mã, câu trả lời này cần phải được đánh gôn. Lấy làm tiếc.
Rɪᴋᴇʀ

1
@Riker Mặc dù tôi không nghĩ rằng mã của mình quá dài dòng để bắt đầu, tôi đã đánh gôn thêm và chịu trách nhiệm xác định kích thước của nó khi khoảng trắng bị vắt kiệt.
Christian Sievers

1
@carusocomputing Tôi sợ nó tệ hơn nhiều. Tôi đang xử lý riêng từng trường hợp phân phối chữ số giữa các lần chạy chữ số, như có một lần chạy ba chữ số, hoặc có một lần chạy hai chữ số và một chữ số, hoặc có ba chữ số, nhưng n=5không có trường hợp có hai chữ số và hai chữ số đơn, vì khi đó chúng ta không có đủ chữ cái để phân tách các số. Đây là những gì ba forvòng lặp bên ngoài làm: chạy qua tất cả các phân vùng hữu ích của các số <n. (Và tôi chỉ nhận ra rằng tôi cũng cho phép các nchữ số. May mắn thay, một tối ưu hóa khác được tính là 0).
Christian Sievers

1
@carusocomputing Lưu ý rằng đối với số <n/2, tất cả các phân vùng đều hữu ích. Và các tính toán còn lại vẫn mất thời gian không đổi. Để xem những gì đang xảy ra, bạn có thể thêmPrint(p,"\n"); vào đầu của for p...vòng lặp. - Tôi có một ý tưởng cho việc sử dụng một vòng lặp ít hơn, nhưng nó sẽ chỉ giúp kích thước mã.
Christian Sievers

2
Tôi nhận được một tốc độ đáng kinh ngạc bằng cách di chuyển mod(đã giúp rất nhiều) vào Value, thay đổi nó thành Value(d mod x^(1+QuoInt(s(l)-1,i-1)),x^(i-1)). Điều đó một mình cho phép tính toán f(15)trong 80 giây.
Christian Sievers

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.