私はこの質問に出くわしました。その桁の合計とその桁の二乗の合計が素数である場合、その数はラッキーと呼ばれます。 AとBの間の数字はいくつラッキーですか? 1 <= A <= B <= 1018。私はこれを試しました。
これは実装です:
#include<stdio.h>
#include<malloc.h>
#include<math.h>
#include <stdlib.h>
#include<string.h>
long long luckynumbers;
int primelist[1500];
int checklucky(long long possible,long long a,long long b){
int prime =0;
while(possible>0){
prime+=pow((possible%10),(float)2);
possible/=10;
}
if(primelist[prime]) return 1;
else return 0;
}
long long getmax(int numdigits){
if(numdigits == 0) return 1;
long long maxnum =10;
while(numdigits>1){
maxnum = maxnum *10;
numdigits-=1;
}
return maxnum;
}
void permuteandcheck(char *topermute,int d,long long a,long long b,int digits){
if(d == strlen(topermute)){
long long possible=atoll(topermute);
if(possible >= getmax(strlen(topermute)-1)){ // to skip the case of getting already read numbers like 21 and 021(permuted-210
if(possible >= a && possible <= b){
luckynumbers++;
}
}
}
else{
char lastswap ='\0';
int i;
char temp;
for(i=d;i<strlen(topermute);i++){
if(lastswap == topermute[i])
continue;
else
lastswap = topermute[i];
temp = topermute[d];
topermute[d] = topermute[i];
topermute[i] = temp;
permuteandcheck(topermute,d+1,a,b,digits);
temp = topermute[d];
topermute[d] = topermute[i];
topermute[i] = temp;
}
}
}
void findlucky(long long possible,long long a,long long b,int digits){
int i =0;
if(checklucky(possible,a,b)){
char topermute[18];
sprintf(topermute,"%lld",possible);
permuteandcheck(topermute,0,a,b,digits);
}
}
void partitiongenerator(int k,int n,int numdigits,long long possible,long long a,long long b,int digits){
if(k > n || numdigits > digits-1 || k > 9) return;
if(k == n){
possible+=(k*getmax(numdigits));
findlucky(possible,a,b,digits);
return;
}
partitiongenerator(k,n-k,numdigits+1,(possible + k*getmax(numdigits)),a,b,digits);
partitiongenerator(k+1,n,numdigits,possible,a,b,digits);
}
void calcluckynumbers(long long a,long long b){
int i;
int numdigits = 0;
long long temp = b;
while(temp > 0){
numdigits++;
temp/=10;
}
long long maxnum =getmax(numdigits)-1;
int maxprime=0,minprime =0;
temp = maxnum;
while(temp>0){
maxprime+=(temp%10);
temp/=10;
}
int start = 2;
for(;start <= maxprime ;start++){
if(primelist[start]) {
partitiongenerator(0,start,0,0,a,b,numdigits);
}
}
}
void generateprime(){
int i = 0;
for(i=0;i<1500;i++)
primelist[i] = 1;
primelist[0] = 0;
primelist[1] = 0;
int candidate = 2;
int topCandidate = 1499;
int thisFactor = 2;
while(thisFactor * thisFactor <= topCandidate){
int mark = thisFactor + thisFactor;
while(mark <= topCandidate){
*(primelist + mark) = 0;
mark += thisFactor;
}
thisFactor++;
while(thisFactor <= topCandidate && *(primelist+thisFactor) == 0) thisFactor++;
}
}
int main(){
char input[100];
int cases=0,casedone=0;
long long a,b;
generateprime();
fscanf(stdin,"%d",&cases);
while(casedone < cases){
luckynumbers = 0;
fscanf(stdin,"%lld %lld",&a,&b);
int i =0;
calcluckynumbers(a,b);
casedone++;
}
}
アルゴリズムが遅すぎます。数字の性質から答えが見つかると思いますので、よろしくお願いします。ありがとうございました。
優れたソリューションOleGGですが、コードは最適化されていません。コードに次の変更を加えました。
Count_lucky関数のkに対して9 * 9 * iを実行する必要はありません。これは、10000の場合、何度も実行されるため、代わりに開始と終了までこの値を減らしました。
中間結果を格納するためにans配列を使用しました。それほど多くはないように見えるかもしれませんが、10000を超えるケースは、これが時間を短縮する主な要因です。
私はこのコードをテストし、すべてのテストケースに合格しました。変更されたコードは次のとおりです。
#include <stdio.h>
const int MAX_LENGTH = 18;
const int MAX_SUM = 162;
const int MAX_SQUARE_SUM = 1458;
int primes[1460];
unsigned long long dyn_table[20][164][1460];
//changed here.......1
unsigned long long ans[19][10][164][1460]; //about 45 MB
int start[19][163];
int end[19][163];
//upto here.........1
void gen_primes() {
for (int i = 0; i <= MAX_SQUARE_SUM; ++i) {
primes[i] = 1;
}
primes[0] = primes[1] = 0;
for (int i = 2; i * i <= MAX_SQUARE_SUM; ++i) {
if (!primes[i]) {
continue;
}
for (int j = 2; i * j <= MAX_SQUARE_SUM; ++j) {
primes[i*j] = 0;
}
}
}
void gen_table() {
for (int i = 0; i <= MAX_LENGTH; ++i) {
for (int j = 0; j <= MAX_SUM; ++j) {
for (int k = 0; k <= MAX_SQUARE_SUM; ++k) {
dyn_table[i][j][k] = 0;
}
}
}
dyn_table[0][0][0] = 1;
for (int i = 0; i < MAX_LENGTH; ++i) {
for (int j = 0; j <= 9 * i; ++j) {
for (int k = 0; k <= 9 * 9 * i; ++k) {
for (int l = 0; l < 10; ++l) {
dyn_table[i + 1][j + l][k + l*l] += dyn_table[i][j][k];
}
}
}
}
}
unsigned long long count_lucky (unsigned long long maxp) {
unsigned long long result = 0;
int len = 0;
int split_max[MAX_LENGTH];
while (maxp) {
split_max[len] = maxp % 10;
maxp /= 10;
++len;
}
int sum = 0;
int sq_sum = 0;
unsigned long long step_result;
unsigned long long step_;
for (int i = len-1; i >= 0; --i) {
step_result = 0;
int x1 = 9*i;
for (int l = 0; l < split_max[i]; ++l) {
//changed here........2
step_ = 0;
if(ans[i][l][sum][sq_sum]!=0)
{
step_result +=ans[i][l][sum][sq_sum];
continue;
}
int y = l + sum;
int x = l*l + sq_sum;
for (int j = 0; j <= x1; ++j) {
if(primes[j + y])
for (int k=start[i][j]; k<=end[i][j]; ++k) {
if (primes[k + x]) {
step_result += dyn_table[i][j][k];
step_+=dyn_table[i][j][k];
}
}
}
ans[i][l][sum][sq_sum] = step_;
//upto here...............2
}
result += step_result;
sum += split_max[i];
sq_sum += split_max[i] * split_max[i];
}
if (primes[sum] && primes[sq_sum]) {
++result;
}
return result;
}
int main(int argc, char** argv) {
gen_primes();
gen_table();
//changed here..........3
for(int i=0;i<=18;i++)
for(int j=0;j<=163;j++)
{
for(int k=0;k<=1458;k++)
if(dyn_table[i][j][k]!=0ll)
{
start[i][j] = k;
break;
}
for(int k=1460;k>=0;k--)
if(dyn_table[i][j][k]!=0ll)
{
end[i][j]=k;
break;
}
}
//upto here..........3
int cases = 0;
scanf("%d",&cases);
for (int i = 0; i < cases; ++i) {
unsigned long long a, b;
scanf("%lld %lld", &a, &b);
//changed here......4
if(b == 1000000000000000000ll)
b--;
//upto here.........4
printf("%lld\n", count_lucky(b) - count_lucky(a-1));
}
return 0;
}
説明:
gen_primes()とgen_table()はほとんど自明です。
count_lucky()は次のように機能します。
split_max []で数値を分割し、1、10、100などの位置に1桁の数値を格納するだけです。アイデアは次のとおりです。split_map[2] = 7とすると、次の結果を計算する必要があります。
百の位置に1つ、すべて00から99。
数百の位置に2つ、すべて00から99。
。 。
数百の位置に7、すべて00から99。
これは実際には(lループで)事前に計算された桁の合計と桁の2乗の合計の観点から行われます。この例の場合:合計は0から9 * iまで変化し、平方和は0から9 * 9 * iまで変化します...これはjループとkループで行われます。これは、iループ内のすべての長さに対して繰り返されます
これがOleGGのアイデアでした。
最適化のために、以下が考慮されます:
0から9 * 9 * iまでの二乗和を実行するのは無意味です。特定の桁の合計については、全範囲に達することはありません。 i = 3で合計が5の場合と同様に、二乗の合計は0から9 * 9 * 3まで変化しません。この部分は、事前に計算された値を使用してstart []およびend []配列に格納されます。
特定の桁数および数値の最上位の特定の桁の値、および特定の合計まで、および特定の平方和までが記憶のために格納されます。長すぎますが、それでも約45MBです。これはさらに最適化できると思います。
このタスクにはDPを使用する必要があります。これが私の解決策です:
#include <stdio.h>
const int MAX_LENGTH = 18;
const int MAX_SUM = 162;
const int MAX_SQUARE_SUM = 1458;
int primes[1459];
long long dyn_table[19][163][1459];
void gen_primes() {
for (int i = 0; i <= MAX_SQUARE_SUM; ++i) {
primes[i] = 1;
}
primes[0] = primes[1] = 0;
for (int i = 2; i * i <= MAX_SQUARE_SUM; ++i) {
if (!primes[i]) {
continue;
}
for (int j = 2; i * j <= MAX_SQUARE_SUM; ++j) {
primes[i*j] = 0;
}
}
}
void gen_table() {
for (int i = 0; i <= MAX_LENGTH; ++i) {
for (int j = 0; j <= MAX_SUM; ++j) {
for (int k = 0; k <= MAX_SQUARE_SUM; ++k) {
dyn_table[i][j][k] = 0;
}
}
}
dyn_table[0][0][0] = 1;
for (int i = 0; i < MAX_LENGTH; ++i) {
for (int j = 0; j <= 9 * i; ++j) {
for (int k = 0; k <= 9 * 9 * i; ++k) {
for (int l = 0; l < 10; ++l) {
dyn_table[i + 1][j + l][k + l*l] += dyn_table[i][j][k];
}
}
}
}
}
long long count_lucky (long long max) {
long long result = 0;
int len = 0;
int split_max[MAX_LENGTH];
while (max) {
split_max[len] = max % 10;
max /= 10;
++len;
}
int sum = 0;
int sq_sum = 0;
for (int i = len-1; i >= 0; --i) {
long long step_result = 0;
for (int l = 0; l < split_max[i]; ++l) {
for (int j = 0; j <= 9 * i; ++j) {
for (int k = 0; k <= 9 * 9 * i; ++k) {
if (primes[j + l + sum] && primes[k + l*l + sq_sum]) {
step_result += dyn_table[i][j][k];
}
}
}
}
result += step_result;
sum += split_max[i];
sq_sum += split_max[i] * split_max[i];
}
if (primes[sum] && primes[sq_sum]) {
++result;
}
return result;
}
int main(int argc, char** argv) {
gen_primes();
gen_table();
int cases = 0;
scanf("%d", &cases);
for (int i = 0; i < cases; ++i) {
long long a, b;
scanf("%lld %lld", &a, &b);
printf("%lld\n", count_lucky(b) - count_lucky(a-1));
}
return 0;
}
簡単な説明:
それで全部です。 O(log(MAX_NUMBER)^ 3)の事前計算作業では、各ステップにもこの複雑さがあります。
私は自分のソリューションを線形の単純なものに対してテストしましたが、結果は同等でした
数字のスペースを列挙する代わりに、幸運な数字のさまざまな「署名」を列挙します。次に、それらのすべての異なる組み合わせを印刷します。
これは、簡単なバックトラックで実行できます。
#define _GNU_SOURCE
#include <assert.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#define bitsizeof(e) (CHAR_BIT * sizeof(e))
#define countof(e) (sizeof(e) / sizeof((e)[0]))
#define BITMASK_NTH(type_t, n) ( ((type_t)1) << ((n) & (bitsizeof(type_t) - 1)))
#define OP_BIT(bits, n, shift, op) \
((bits)[(unsigned)(n) / (shift)] op BITMASK_NTH(typeof(*(bits)), n))
#define TST_BIT(bits, n) OP_BIT(bits, n, bitsizeof(*(bits)), & )
#define SET_BIT(bits, n) (void)OP_BIT(bits, n, bitsizeof(*(bits)), |= )
/* fast is_prime {{{ */
static uint32_t primes_below_1M[(1U << 20) / bitsizeof(uint32_t)];
static void compute_primes_below_1M(void)
{
SET_BIT(primes_below_1M, 0);
SET_BIT(primes_below_1M, 1);
for (uint32_t i = 2; i < bitsizeof(primes_below_1M); i++) {
if (TST_BIT(primes_below_1M, i))
continue;
for (uint32_t j = i * 2; j < bitsizeof(primes_below_1M); j += i) {
SET_BIT(primes_below_1M, j);
}
}
}
static bool is_prime(uint64_t n)
{
assert (n < bitsizeof(primes_below_1M));
return !TST_BIT(primes_below_1M, n);
}
/* }}} */
static uint32_t prime_checks, found;
static char sig[10];
static uint32_t sum, square_sum;
static void backtrack(int startdigit, int ndigits, int maxdigit)
{
ndigits++;
for (int i = startdigit; i <= maxdigit; i++) {
sig[i]++;
sum += i;
square_sum += i * i;
prime_checks++;
if (is_prime(sum) && is_prime(square_sum)) {
found++;
}
if (ndigits < 18)
backtrack(0, ndigits, i);
sig[i]--;
sum -= i;
square_sum -= i * i;
}
}
int main(void)
{
compute_primes_below_1M();
backtrack(1, 0, 9);
printf("did %d signature checks, found %d lucky signatures\n",
prime_checks, found);
return 0;
}
私がそれを実行すると、それはします:
$ time ./lucky
did 13123091 signature checks, found 933553 lucky signatures
./lucky 0.20s user 0.00s system 99% cpu 0.201 total
Found ++の代わりに、その番号で作成できる数字のすべての異なる順列を生成する必要があります。また、これまでに最初の1Mの素数を事前計算します。
コードが100%正しいかどうかは確認していません。少しデバッグする必要があるかもしれません。しかし、大まかなアイデアはここにあり、0.2秒未満のすべての幸運な順列を生成することができます(バグがなくても、2倍以上遅くなることはありません)。
そしてもちろん、A <= Bを検証する順列を生成する必要があります。Bより多い、またはAより少ない桁数のパーティションの生成も無視することができます。とにかく、ここから私の一般的な考えを改善することができます。
(注:最初の宣伝文句は、プロジェクトオイラー用に作成したコードをカットアンドペーストしたためです。したがって、N <= 1Mで機能する非常に高速なis_primeです;))
まだ気付いていない人にとっては、これはウェブサイトInterviewStreet.comの問題です(そして私の意見では、そこで最も難しい問題です)。私のアプローチは、以下のOleGGと同様に(そしてそれに触発されて)始まりました。しかし、彼が作成した最初の[19] [163] [1459]テーブル(これをtable1と呼びます)を作成した後、私は少し異なる方向に進みました。不規則な長さの2番目のテーブル[19] [x] [3](table2)を作成しました。ここで、xは、対応する桁数の一意の合計ペアの数です。また、長さが3のテーブルの3番目の次元の場合、1番目の要素は、2番目と3番目の要素が保持するsum値とsquareSum値を持つ一意の「合計ペア」の数です。
例えば:
//pseudocode
table2[1] = new long[10][3]
table2[1] = {{1, 0, 0}, {1, 1, 1}, {1, 2, 4},
{1, 3, 9}, {1, 4, 16}, {1, 5, 25},
{1, 6, 36}, {1, 7, 49}, {1, 8, 64}, {1, 9, 81}}
table2[2] = new long[55][3]
table2[3] = new long[204][3]
table2[4] = new long[518][3]
.
.
.
.
table2[17] = new long[15552][3]
table2[18] = new long[17547][3]
配列の2番目の次元の長さ(10、55、204、518、...、15552、17547)について私が持っている数値は、table1にクエリを実行することで確認でき、同様の方法でtable2にデータを入力できます。 table2を使用すると、OleGGの投稿されたメソッドよりもはるかに高速に大きな「ラッキー」クエリを解決できますが、それでも彼と同様の「分割」プロセスを採用しています。たとえば、lucky(00000-54321)(つまり、0から54321までのラッキー番号)を見つける必要がある場合、次の5行の合計に分解されます。
lucky(00000-54321) = {
lucky(00000-49999) +
lucky(50000-53999) +
lucky(54000-54299) +
lucky(54300-53319) +
lucky(54320-54321)
}
これはさらに分解されます:
lucky(00000-49999) = {
lucky(00000-09999) +
lucky(10000-19999) +
lucky(20000-29999) +
lucky(30000-39999) +
lucky(40000-49999)
}
.
.
lucky(54000-54299) = {
lucky(54000-54099) +
lucky(54100-54199) +
lucky(54200-54299)
}
.
.
.
etc
これらの各値は、table2をクエリすることで簡単に取得できます。たとえば、lucky(40000-49999)は、3次元テーブル2の2番目と3番目の要素に4と16を追加することで見つかります。
sum = 0
for (i = 0; i < 518; i++)
if (isPrime[table2[4][i][1] + 4] && isPrime[table2[4][i][2] + 4*4])
sum += table2[4][i][0]
return sum
または幸運のために(54200-54299):
sum = 0
for (i = 0; i < 55; i++)
if (isPrime[table2[2][i][1] + (5+4+2)]
&& isPrime[table2[2][i][2] + (5*5+4*4+2*2)])
sum += table2[2][i][0]
return sum
現在、OleGGのソリューションは、それまで試した他のどのソリューションよりも大幅に高速でしたが、上記の変更により、以前よりもさらに優れたパフォーマンスを発揮します(大規模なテストセットの場合、約100倍)。ただし、InterviewStreetで提供されるブラインドテストケースにはまだ十分な速度ではありません。いくつかの巧妙なハックを通して、私は現在、割り当てられた時間内にテストセットを完了するには約20倍遅すぎると判断することができました。しかし、それ以上の最適化は見つかりません。ここでの最大のタイムシンクは、明らかにtable2の2番目の次元を反復処理することであり、それを回避する唯一の方法は、これらの合計の結果を表にすることです。ただし、指定された時間(5秒)ですべてを計算したり、指定されたスペース(256MB)にすべてを格納したりする可能性は多すぎます。たとえば、上記のlucky(54200-54299)ループは事前に計算され、単一の値として格納されますが、そうである場合は、lucky(123000200-123000299)とlucky(99999200-99999299)も事前に計算する必要があります。 )など。私は計算を行いましたが、事前に計算するには計算が多すぎます。
要件に基づいて、さまざまな方法でそれを行うことができます。私がそれをしているなら、私は必要な範囲(Aから(9 * 2)* B.length)で「エラトステネスのふるい」を使用して素数を計算し、それらをキャッシュします(ここでも、セットアップに応じて、で使用できます-メモリまたはディスクキャッシュ)、次の実行に使用します。
以下のように、高速ソリューション(Java)をコーディングしました([〜#〜] note [〜#〜]:整数オーバーフローはチェックされません。単なる高速な例です。また、コードが最適化されていません。):
import Java.util.ArrayList;
import Java.util.Arrays;
public class LuckyNumbers {
public static void main(String[] args) {
int a = 0, b = 1000;
LuckyNumbers luckyNums = new LuckyNumbers();
ArrayList<Integer> luckyList = luckyNums.findLuckyNums(a, b);
System.out.println(luckyList);
}
private ArrayList<Integer> findLuckyNums(int a, int b) {
ArrayList<Integer> luckyList = new ArrayList<Integer>();
int size = ("" + b).length();
int maxNum = 81 * 4; //9*2*b.length() - 9 is used, coz it's the max digit
System.out.println("Size : " + size + " MaxNum : " + maxNum);
boolean[] primeArray = sieve(maxNum);
for(int i=a;i<=b;i++) {
String num = "" + i;
int sumDigits = 0;
int sumSquareDigits = 0;
for(int j=0;j<num.length();j++) {
int digit = Integer.valueOf("" + num.charAt(j));
sumDigits += digit;
sumSquareDigits += Math.pow(digit, 2);
}
if(primeArray[sumDigits] && primeArray[sumSquareDigits]) {
luckyList.add(i);
}
}
return luckyList;
}
private boolean[] sieve(int n) {
boolean[] prime = new boolean[n + 1];
Arrays.fill(prime, true);
prime[0] = false;
prime[1] = false;
int m = (int) Math.sqrt(n);
for (int i = 2; i <= m; i++) {
if (prime[i]) {
for (int k = i * i; k <= n; k += i) {
prime[k] = false;
}
}
}
return prime;
}
}
そして、出力は次のとおりです。
[11、12、14、16、21、23、25、32、38、41、49、52、56、58、61、65、83、85、94、101、102、104、106、110、111 、113、119、120、131、133、137、140、146、160、164、166、173、179、191、197、199、201、203、205、210、223、229、230、232、250 、289、292、298、302、308、311、313、317、320、322、331、335、337、344、346、353、355、364、368、371、373、377、379、380、386 、388、397、401、409、410、416、434、436、443、449、461、463、467、476、490、494、502、506、508、520、533、535、553、559、560 、566、580、595、601、605、610、614、616、634、638、641、643、647、650、656、661、665、674、683、689、698、713、719、731、733 、737、739、746、764、773、779、791、793、797、803、805、829、830、836、838、850、863、869、883、892、896、904、911、917、919 、922、928、937、940、944、955、968、971、973、977、982、986、991]
私はピエールの列挙法を使用して解決策を考え出そうとしましたが、順列を数えるのに十分な速さの方法を思いつきませんでした。 OleGGのカウント方法は非常に巧妙であり、海賊の最適化はそれを十分に速くするために必要です。私は1つの小さな改善と、深刻な問題に対する1つの回避策を思いつきました。
まず、改善点です。海賊のjループとkループの素数をチェックするために、すべての合計と二乗和を1つずつ確認する必要はありません。素数のリストがあります(または簡単に生成できます)。他の変数を使用してどの素数が範囲内にあるかを把握する場合は、sumとsquaresumに適した素数のリストをステップスルーするだけです。素数の配列とルックアップテーブルを使用して、素数> =の数値がどのインデックスにあるかをすばやく判断すると便利です。ただし、これはおそらくかなり小さな改善にすぎません。
大きな問題は、海賊のansキャッシュ配列にあります。主張されているように45MBではありません。 64ビットエントリの場合、364MBのようなものです。これは、CおよびJavaで(現在)許可されているメモリ制限の範囲外です。 「l」ディメンションを削除することで37MBに減らすことができます。これは不要であり、とにかくキャッシュのパフォーマンスを低下させます。 l、sum、squaresumを個別にキャッシュするのではなく、l + sumとl * l + squaresumのカウントをキャッシュすることに本当に関心があります。
最速のソリューションが信じられないほど単純な場合もあります。
uint8_t precomputedBitField[] = {
...
};
bool is_lucky(int number) {
return precomputedBitField[number >> 8] & (1 << (number & 7));
}
既存のコードを変更して「precomputedBitField」を生成するだけです。
サイズが気になる場合は、0から999までのすべての数値をカバーするには、125バイトしかかかりません。したがって、この方法は、他のどの方法よりもおそらく小さくなります(そしてはるかに高速になります)。
私はあなたの現在の解決策を注意深く分析していませんが、これはそれを改善するかもしれません:
数字の順序は重要ではないため、長さ1〜18の数字0〜9のすべての可能な組み合わせを調べ、数字の合計とその2乗を追跡し、前の結果を使用して一度に1桁ずつ追加する必要があります。計算。
したがって、12の数字の合計が3で、平方の合計が5であることがわかっている場合は、120、121、122 ...などの数値を見て、12の3と5から簡単に合計を計算します。