素数テーブルをO(n)で構築する
から
までの数が素数であるかどうかの配列(素数テーブル)を
で構築する。
はじめに
まず、このような素数テーブルの構築といえばいわゆるエラトステネスのふるいが有名です。小さい方から、その倍数にバツマークを付けていくと、で構築できます。実装も素直で簡単です:
const int N = 10000001; bool prime[N]; void build_sieve(){ fill(prime,prime+N,true); prime[0] = prime[1] = false; for(int i=2; i<N; ++i)if(prime[i]){ for(int j=2*i; j<N; j+=i) prime[j] = false; } }
実装
次にの実装を見てみます。
const int N = 10000001; int mind[N]; vector<int> pr; void build_sieve(){ mind[0] = mind[1] = 1; for(int i=2; i<N; ++i){ if(mind[i] == 0){ pr.push_back(i); mind[i] = i; } for (int j=0; j<int(pr.size()) && pr[j]<=mind[i] && i*pr[j]<N; ++j){ mind[i*pr[j]] = pr[j]; } } }
説明
まず、は素数を小さい順に持っています。
そして、という配列が何を情報として持っているのか見ると、mind[x]は「
を割り切る1より大きい最小の数」を持っています(0,1は例外)。
ループ内の処理を見ていくと、if文の中はが素数であることが確定しています。すでに
までの処理が終わっていて、この時点でmindの更新が入っていないということは
mind[i] = i
であることが確定するということです。
つぎにその下ののループに注目します。これは
が素数であるか否かに関係なく回っています。mind[i]を超えない範囲の素数を小さい順に見て行きます。
2つの数の積なので、i*pr[j]
は合成数です。ここで、を割り切る1より大きい最小の数は
mind[i]
なので、mind[i*pr[j]] = pr[j]
は正しいです(pr[j] <= mind[i]
なので)。
これがなぜうまくいっているのかについて考えると、合成数は (
は素数)の形で表せます。更に、この
が選べるものの中で最小であると仮定すると、
mind[n] = p
になります。そして、この更新のタイミングは必ずの時にのみ来るということです。なので、mind配列の各indexへのアクセスはちょうど1回なので、この実装が
であると分かります。
さいごに
おおよその問題はで十分だと思います。
むしろこれが必要になるほどTime Limitがキツい問題はちょっと...
参考
https://codeforces.com/blog/entry/66586 ← ここに実装が書いてあります
CF 631 E - Product Sum
問題
問題概要
長さの1-indexedな配列
がある。この数列に対して一度だけ、数列のある要素を選び、その要素を数列内の好きな位置に移動するという操作をすることができる。
このとき、の取りうる最大値を求めよ。
アイデア
何も動かさなかったときのの値を
として、動かしたときの差分
について考える。
以下、2つの場合について考える。
ある要素を右に動かす場合
番目の要素を、
番目の要素となるように動かすとする(
)。
このとき、操作前と操作後でindexはこのようになる。
よって、indexから
までの
の和を
とすると、
となり、これはCHTを利用し、を減らしていきながら探索することで
で探索することができる(傾きには単調性がある方向で追加している)。
ある要素を左に動かす場合
番目の要素を、
番目の要素となるように動かすとする(
)。
これも同様に計算すると、となり、今度は
を基準にすることで同様に探索ができる。