जीएपी , 416 बाइट्स
कोड आकार और लगातार समय से बहुत दूर नहीं जीतेंगे, लेकिन गणित का उपयोग बहुत तेजी से करते हैं!
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;
अनावश्यक व्हॉट्सएप को निचोड़ने और 416 बाइट्स के साथ एक लाइन प्राप्त करने के लिए, इसके माध्यम से पाइप करें:
sed -e 's/^ *//' -e 's/in \[/in[/' -e 's/ do/do /' | tr -d \\n
मेरा पुराना "विंडोज एक्सपी के लिए डिज़ाइन किया गया" लैपटॉप f(10)
एक मिनट से भी कम समय में गणना कर सकता है और एक घंटे के भीतर बहुत आगे बढ़ सकता है:
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
यह काम किस प्रकार करता है
मान लीजिए कि हम पहले पैटर्न को फिट करने वाले पूर्ण लाइसेंस प्लेटों की संख्या जानना चाहते हैं LDDLLDL
, जहां L
एक अक्षर को D
दर्शाता है और
एक अंक को दर्शाता है। मान लें कि हमारे पास l
संख्याओं की
एक सूची l[i]
है जो अक्षरों को मूल्य देने के तरीकों की संख्या बताती है i
, और d
अंकों से प्राप्त मूल्यों के लिए एक समान सूची । फिर सामान्य मूल्य के साथ सही लाइसेंस प्लेटों की संख्या i
बस है
l[i]*d[i]
, और हम अपने सभी पैटर्न के साथ सभी पूर्ण लाइसेंस प्लेटों की संख्या प्राप्त करते हैं i
। आइए इस योग को प्राप्त करने के संचालन को निरूपित करते हैं l@d
।
अब भले ही इन सूचियों को प्राप्त करने का सबसे अच्छा तरीका सभी संयोजनों और गिनती की कोशिश करना था, हम अक्षरों और अंकों के लिए स्वतंत्र रूप से ऐसा कर सकते हैं, 26^4+10^3
मामलों के बजाय 26^4*10^3
मामलों को देखते हुए जब हम सभी प्लेटों को पैटर्न फिटिंग करते हैं। लेकिन हम बहुत बेहतर कर सकते हैं: l
सिर्फ अक्षरों की सूची है,
(x+x^2+...+x^26)^k
जहां k
पत्रों की संख्या है, यहां 4
।
इसी प्रकार, हम k
गुणांक के रूप में अंकों की एक संख्या में अंकों का योग प्राप्त करने के तरीकों की संख्या प्राप्त करते हैं (1+x+...+x^9)^k
। यदि अंकों का एक से अधिक भाग है, तो हमें संबंधित सूचियों को एक ऑपरेशन के साथ संयोजित करना होगा d1#d2
जो कि उस स्थान पर i
हो d1[i1]*d2[i2]
जहाँ सभी का योग हो i1*i2=i
। यह डिरिचलेट कनवल्शन है, जो सिर्फ उत्पाद है अगर हम लिस्ट को डार्चलेट श्रृंखला के गुणांक के रूप में व्याख्या करते हैं। लेकिन हमने पहले ही उन्हें बहुपद (परिमित शक्ति श्रृंखला) के रूप में उपयोग किया है, और उनके लिए ऑपरेशन की व्याख्या करने का कोई अच्छा तरीका नहीं है। मुझे लगता है कि यह बेमेल हिस्सा है जो एक सरल सूत्र को खोजने के लिए कठिन बनाता है। चलो वैसे भी बहुपद पर इसका उपयोग करें और एक ही अंकन का उपयोग करें #
। गणना करना आसान है जब एक ऑपरेंड एक मोनोमियल है: हमारे पासp(x) # x^k = p(x^k)
। इस तथ्य के साथ कि यह बिलिनियर है, यह गणना करने के लिए एक अच्छा (लेकिन बहुत कुशल नहीं) तरीका देता है।
ध्यान दें कि k
अक्षर अधिकतम मूल्य देते हैं 26k
, जबकि k
एकल अंक एक मान दे सकते हैं 9^k
। इसलिए हम d
बहुपद में अक्सर अनावश्यक शक्तियों को प्राप्त करेंगे । इनसे छुटकारा पाने के लिए हम मोडुलो की गणना कर सकते हैं x^(maxlettervalue+1)
। यह एक बड़ी गति देता है और, हालांकि, मैंने तुरंत ध्यान नहीं दिया, यहां तक कि गोल्फिंग में भी मदद करता है, क्योंकि अब हम जानते हैं कि डिग्री d
बड़ी नहीं होती है l
, जो अंतिम में ऊपरी सीमा को सरल बनाती है Sum
। हम
( mod
पहले Value
टिप्पणियों को देखें) में एक गणना करके और भी बेहतर गति प्राप्त करते हैं, और #
निचले स्तर पर पूरी गणना करने से एक अविश्वसनीय गति मिलती है। लेकिन हम अभी भी एक गोल्फ समस्या के लिए एक वैध जवाब बनने की कोशिश कर रहे हैं।
तो हम अपने मिल गया है l
और d
पैटर्न के साथ सही लाइसेंस प्लेटों की संख्या की गणना करने के लिए उनका उपयोग कर सकते हैं LDDLLDL
। पैटर्न के लिए भी यही संख्या है LDLLDDL
। सामान्य तौर पर, हम अपनी पसंद के अनुसार विभिन्न लंबाई के अंकों के क्रम को बदल सकते हैं,
NrArrangements
संभावनाओं की संख्या देता है। और जबकि अंकों के रनों के बीच एक अक्षर होना चाहिए, अन्य अक्षर निश्चित नहीं हैं। Binomial
इन संभावनाओं गिना जाता है।
अब यह रन डिजिट की लंबाई होने के सभी संभावित तरीकों से चलता रहता है। r
सभी अंकों की c
कुल संख्याओं के p
माध्यम से , और अंकों के c
साथ
सभी विभाजनों के माध्यम से चलाता है r
।
हमारे द्वारा देखे जाने वाले विभाजन की कुल संख्या विभाजन की संख्या से दो कम है n+1
, और विभाजन कार्य जैसे बढ़ते हैं
exp(sqrt(n))
। इसलिए जबकि परिणामों को पुन: उपयोग करके (एक अलग क्रम में विभाजनों के माध्यम से) चल रहे समय में सुधार करने के लिए अभी भी आसान तरीके हैं, एक मौलिक सुधार के लिए हमें प्रत्येक विभाजन को अलग-अलग देखने से बचने की आवश्यकता है।
यह तेजी से कम्प्यूटिंग
ध्यान दें (p+q)@r = p@r + q@r
। अपने दम पर, यह सिर्फ कुछ गुणा से बचने में मदद करता है। लेकिन इसके साथ-साथ (p+q)#r = p#r + q#r
इसका मतलब है कि हम अलग-अलग विभाजनों के अनुरूप सरल जोड़ पॉलीओनॉमिक्स द्वारा जोड़ सकते हैं। हम उन सभी को नहीं जोड़ सकते हैं, क्योंकि हमें अभी भी यह जानना होगा कि l
हमारे पास कौन-कौन से @
-combine हैं, हमें किस कारक का उपयोग करना है, और कौन #
-से-अभी भी संभव हैं।
आइए एक ही राशि और लंबाई के साथ विभाजन के अनुरूप सभी बहुपदों को मिलाएं, और पहले से ही अंकों के रनों की लंबाई को वितरित करने के कई तरीकों के लिए खाते हैं। टिप्पणियों में मैंने जो भी अनुमान लगाया है, उससे अलग, मुझे सबसे कम इस्तेमाल होने वाले मूल्य या कितनी बार इसका उपयोग करने की आवश्यकता है, अगर मुझे यकीन है कि मैं उस मूल्य के साथ विस्तार नहीं करूंगा।
यहाँ मेरा C ++ कोड है:
#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" ;
}
यह GNU MP लाइब्रेरी का उपयोग करता है। डेबियन पर, स्थापित करें libgmp-dev
। के साथ संकलित करें g++ -std=c++11 -O3 -o pl pl.cpp -lgmp -lgmpxx
। कार्यक्रम स्टड से अपना तर्क लेता है। समय के लिए, का उपयोग करें echo 100 | time ./pl
।
अंत में a[sum][length][i]
जिन तरीकों की संख्या देता है sum
में अंकों length
रन नंबर दे सकते हैं i
। गणना के दौरान, m
लूप की शुरुआत में , यह उन तरीकों की संख्या देता है जो संख्या से अधिक के साथ किया जा सकता है m
। यह सब के साथ शुरू होता है
a[0][0][1]=1
। ध्यान दें कि यह संख्याओं का एक सुपरसेट है जिसे हमें छोटे मानों के लिए फ़ंक्शन की गणना करने की आवश्यकता है। तो लगभग एक ही समय में, हम सभी मूल्यों की गणना कर सकते हैं n
।
कोई पुनरावृत्ति नहीं है, इसलिए हमारे पास निश्चित संख्या में नेस्टेड लूप हैं। (सबसे गहरा घोंसला स्तर 6. है।) प्रत्येक लूप कई मूल्यों से गुजरता n
है जो सबसे खराब स्थिति में रैखिक है । इसलिए हमें केवल बहुपद समय की आवश्यकता है। यदि हम नेस्टेड i
और अंदर j
छोरों को करीब से देखते हैं extend
, तो हम j
फॉर्म के लिए एक ऊपरी सीमा पाते हैं N/i
। यह केवल j
लूप के लिए एक लघुगणकीय कारक देना चाहिए । f
( sumn
आदि के साथ ) अंतरतम पाश समान है। यह भी ध्यान रखें कि हम संख्याओं के साथ गणना करते हैं जो तेजी से बढ़ती हैं।
ध्यान दें कि हम O(n^3)
इन नंबरों को स्टोर करते हैं।
प्रयोगात्मक रूप से, मुझे ये परिणाम उचित हार्डवेयर (i5-4590S) पर मिलते हैं:
f(50)
एक सेकंड और 23 एमबी, f(100)
21 सेकंड और 166 एमबी की f(200)
आवश्यकता होती है , 10 मिनट और 1.5 जीबी की f(300)
आवश्यकता होती है , और एक घंटे और 5.6 जीबी की आवश्यकता होती है। यह एक समय जटिलता से बेहतर सुझाव देता है O(n^5)
।
N
।