プログラミングのお題スレ Part22 (854レス)
前次1-
抽出解除 必死チェッカー(本家) (べ) 自ID レス栞 あぼーん

リロード規制です。10分ほどで解除するので、他のブラウザへ避難してください。
759: デフォルトの名無しさん [sage] 2025/04/17(木) 00:27:46.72 ID:dz0qzhSq(1/3) AAS
素因数和2025のお題の3系統のコードを読み解いてみた

>>756
756(2): 753 [sage] 2025/04/13(日) 11:09:12.37 ID:vq5HB/06(1) AAS
>>712
初Rust
外部リンク:ideone.com
2000万まで各々で素因数の和を求めて2025になるかを確認する方法。
ただし各数の素因数分解を工夫せずにすると大変なので、
各数の最小素因数(SPF)を先に一気にエラトステネスの篩で求めてる。
それを用いれば各数の素因数分解はその分解数回の割算だけで求まる。

ただしそのSPF表のために長さ2000万のベクタを用いている。
もし20億までなら32bit✕20億で8GBが許容できるとしても、
あるいはこのSPF一覧のベクタを用いなくても、
20億回の処理を劇的に減らす枝刈り方法を組み込めないと厳しい。
2000万までの計算でも一番遅い。
760: デフォルトの名無しさん [sage] 2025/04/17(木) 00:29:06.02 ID:dz0qzhSq(2/3) AAS
>>741
741(4): デフォルトの名無しさん [] 2025/04/09(水) 22:22:33.83 ID:Ip5PiQSs(1/3) AAS
>>738-739
出題時に作成した解答例

C++
外部リンク:ideone.com
R
外部リンク:ideone.com

と解の個数と最小値・最大値が一致するので正解だろう。

ローカルでコンパイルしようとしたら、

 error[E0425]: cannot find function `generate_primes` in this scope

と表示されコンパイルできなかったので、実行時間の比較はできなかった。
2000万個すべてを素因数分解する方法とは逆に、
2025以下の素数の組み合わせを調べていく方法。
そのうち和が2025になる組み合わせの積が各解となる。

それを効率よく求めるために2025(+1)個のベクタのベクタpを用意している。
つまり和がsumとなる時の積をp[sum]に記録していく。
効率面からの仮の初期値p[0]に1を入れて、各素数iについて降順に、
ベクタp[j]にxがある時、ベクタp[i+j]にx*iをpushしていく。
ここでjの上限は 2025 - i、処理するxの上限は 2000万 / i の枝刈りができる。
最終的にベクタp[2025]の一覧が解となる。
2025個のベクタを用いることが長所および短所になっている。

この改良版>>754
754(2): デフォルトの名無しさん [] 2025/04/12(土) 21:18:53.69 ID:RVQAocGC(1) AAS
>>741のC++プログラムは for (int i = S; i >= 2; i--) のループの最後のi = 2の場合を
ループの外に出して最適化 (p[S]以外のp[_]への要素追加をしないように) するだけで、
20億以下で解の出力なしのときの実行時間が元のプログラムの半分未満になるな。

外部リンク:ideone.com (1.02秒)
 ↓
外部リンク:ideone.com (0.44秒)
では、最後の素数2だけ特別扱いすることで倍速にしている。
761: デフォルトの名無しさん [sage] 2025/04/17(木) 00:32:14.51 ID:dz0qzhSq(3/3) AAS
>>738
738(7): デフォルトの名無しさん [sage] 2025/04/08(火) 23:28:40.30 ID:OzdBhfzQ(1/2) AAS
>>712  Rust
fn solve(n: usize, limit: usize) -> Vec<usize> {
 let mut answer = Vec::new();
 let mut pnt = generate_primes(n).iter().skip(1).rev().map(|&p| (p, 0, 0)).collect::<Vec<_>>();
 let (mut ci, mut cn, mut ct) = (0, n, 1_usize);
 'advance: loop {
  pnt[ci..].iter_mut().for_each(|(_p, n, t)| (*n, *t) = (cn, ct));
  if cn & 1 == 0 && ct.leading_zeros() >= (cn >> 1) as u32 {
   ct <<= cn >> 1;
   if ct <= limit { answer.push(ct as usize); }
  }
  for (i, (p, n, t)) in pnt.iter_mut().enumerate().rev() {
   if *n < *p { continue; }
   *n -= *p; *t *= *p;
   if *t > limit { continue; }
   if *n == 1 { continue; }
   if *n == 0 { answer.push(*t as usize); continue; }
   (ci, cn, ct) = (i, *n, *t);
   continue 'advance;
  };
  break;
 }
 answer.sort(); answer
}
これも2025以下の素数を組み合わせて和が2025になる解を調べる方法。
解一覧を収めるベクタ以外は、固定の長さ305のベクタのみが使われている。
この長さの 305 とは2025以下~3以上の素数[2017, 2011, ... 5, 3]の数になってる。
各素数の冪乗の和 2017^e2017 + 2011^e2011 + ... + 5^e5 + 3^e3 及び積を計算していくが、
各指数のe2017などは解に不要なので使われておらず、
各冪乗の和自体も不要なので2025までの残りの数が使われていて、
固定長ベクタの値は(素数, 残りの数, ここまでの積)の3つ組となっている。
その素数を1つ使うと残りの数が減算されてて積が乗算されるのを繰り返していく。
素数2だけ特別扱いされており、残りの数の半分だけ左シフトすると解の積が出来上がる。

各if~continueが枝刈りとなっていて、残りの下限(=和の上限)や積の上限などがある
この改良版>>750
750(3): デフォルトの名無しさん [sage] 2025/04/11(金) 07:38:20.09 ID:oaeJuxMT(1) AAS
>>738 に手を加えて10倍速くしてみた
fn solve(n: usize, limit: usize) -> Vec<usize> {
 let mut answer = Vec::new();
 let mut pnt = generate_primes(n).windows(2).rev().map(|s| (s[1], s[0], 0, 0)).collect::<Vec<_>>();
 let (mut ci, mut cn, mut ct) = (0, n, 1_usize);
 'advance: loop {
  pnt[ci..].iter_mut().for_each(|(_p, _q, n, t)| (*n, *t) = (cn, ct));
  if cn & 1 == 0 && ct.leading_zeros() >= (cn >> 1) as u32 {
   ct <<= cn >> 1; if ct <= limit { answer.push(ct); }
  }
  'back: for (i, (p, q, n, t)) in pnt.iter_mut().enumerate().rev() {
   'again: loop {
    if *n < *p { continue 'back; }
    *n -= *p; *t *= *p;
    if *n ==1 || *t > limit { continue 'back; }
    if *n == 0 { answer.push(*t); continue 'back; }
    if *q > 3 {
     let mut tt = *t * (*n % *q);
     for _ in 0..(*n / *q) { tt *= *q; if tt > limit { continue 'again; } }
    }; break 'again;
   }; (ci, cn, ct) = (i, *n, *t); continue 'advance;
  }; break 'advance;
 }; answer.sort(); answer
}
では、次の素数以降の組み合わせで起き得る積の下限で枝刈りしている。
例えば7以下の素数で残り21ならば積の下限は7*7*7=343となるため、
ここまでの積に343を掛けた値が2000万(または20億)を超えていれば枝刈りできるようだ。

このアルゴリズムは作業メモリが固定で小さく常にL1キャッシュに乗り有利と思われる点と、
様々な枝刈りがしやすく処理する組み合わせを大きく減らしている点により、
他のアルゴリズムより桁違いに高速になっていると推測される。
前次1-
スレ情報 赤レス抽出 画像レス抽出 歴の未読スレ AAサムネイル

ぬこの手 ぬこTOP 0.032s