DPを納得したい
DPというのは不思議で、ただ適当に配列をループで回しているだけに見えても難しい問題を解決してくれていたりします。
実際に解決できているのだから、間違っていることはないのでしょうが、直感的に正しいとはとても思えません。
最近、DPは効率のいい全探索なのかなと思うようになりました。
何があっても、その状態のときに変わらない答えを持つような問題に分割し、それぞれを解く。
この、「何があっても、その状態のときに変わらない答えを持つ」とは、例えばメモ化再帰なら自分自身を呼び出しているところなどです。
この時に、自分自身の呼び出しをロジックとしてではなく、抽象的なAとBのときにCとなる値、として、その値は常に正しいものと仮定してみます。
その値が正しいなら、現在の値も正しい。
たどっていくと、DPは自明な状態に遷移します。
この自明な状態については例えば0などといった値を当てはめてやることで、正しい値とすることができます。
これにより、自明な状態の値によって計算される値、さらにそれを使って計算される値もすべて正しくなります。
数学的帰納法ってやつですかね?
ABC140 C Maximal Value
考察
数列 の和を最大化したいので、できるだけ全ての を に近づけたい。
として問題なさそう。
については が を上回らないのなら
が を上回るのなら とすると辻褄を合わせたまま最大化できそう。
こんな感じで数列 を後ろから見て行って数列 を決定していく。
思ったこと
灰diffなのに難しくない?
C++テンプレートのメモ
struct reporter { reporter() { cout << "construct" << endl; } ~reporter() { cout << "destruct" << endl; } reporter(const reporter &) { cout << "copy" << endl; } reporter(reporter &&) { cout << "move" << endl; } reporter &operator =(const reporter &) { cout << "copy" << endl; return *this; } reporter &operator =(reporter &&) { cout << "move" << endl; return *this; } }; using type = reporter; template <typename T> void f1(T) { cout << "f1(T)" << endl; cout << "type \t" << is_same_v<T, type> << endl; cout << "type & \t" << is_same_v<T, type &> << endl; cout << "type && \t" << is_same_v<T, type &&> << endl; cout << "type * \t" << is_same_v<T, type *> << endl; cout << "type[1] \t" << is_same_v<T, type[1]> << endl; cout << "type(&)[1] \t" << is_same_v<T, type(&)[1]> << endl; cout << "type(&&)[1]\t" << is_same_v<T, type(&&)[1]> << endl; cout << endl; } template <typename T> void f2(T&) { cout << "f2(T&)" << endl; cout << "type \t" << is_same_v<T, type> << endl; cout << "type & \t" << is_same_v<T, type &> << endl; cout << "type && \t" << is_same_v<T, type &&> << endl; cout << "type * \t" << is_same_v<T, type *> << endl; cout << "type[1] \t" << is_same_v<T, type[1]> << endl; cout << "type(&)[1] \t" << is_same_v<T, type(&)[1]> << endl; cout << "type(&&)[1]\t" << is_same_v<T, type(&&)[1]> << endl; cout << endl; } template <typename T> void f3(T&&) { cout << "f3(T&&)" << endl; cout << "type \t" << is_same_v<T, type> << endl; cout << "type & \t" << is_same_v<T, type &> << endl; cout << "type && \t" << is_same_v<T, type &&> << endl; cout << "type * \t" << is_same_v<T, type *> << endl; cout << "type[1] \t" << is_same_v<T, type[1]> << endl; cout << "type(&)[1] \t" << is_same_v<T, type(&)[1]> << endl; cout << "type(&&)[1]\t" << is_same_v<T, type(&&)[1]> << endl; cout << endl; } int main() { cout << "type" << endl; type v; f1(v); f2(v); f3(v); cout << "type &" << endl; type &r1 = v; f1(r1); f2(r1); f3(r1); cout << "type &&" << endl; type &&r2 = type(); f1(r2); f2(r2); f3(r2); cout << "type *" << endl; type *p = nullptr; f1(p); f2(p); f3(p); cout << "type[]" << endl; type a[1]; f1(a); f2(a); f3(a); }
type construct copy f1(T) type 1 type & 0 type && 0 type * 0 type[1] 0 type(&)[1] 0 type(&&)[1] 0 destruct f2(T&) type 1 type & 0 type && 0 type * 0 type[1] 0 type(&)[1] 0 type(&&)[1] 0 f3(T&&) type 0 type & 1 type && 0 type * 0 type[1] 0 type(&)[1] 0 type(&&)[1] 0 type & copy f1(T) type 1 type & 0 type && 0 type * 0 type[1] 0 type(&)[1] 0 type(&&)[1] 0 destruct f2(T&) type 1 type & 0 type && 0 type * 0 type[1] 0 type(&)[1] 0 type(&&)[1] 0 f3(T&&) type 0 type & 1 type && 0 type * 0 type[1] 0 type(&)[1] 0 type(&&)[1] 0 type && construct copy f1(T) type 1 type & 0 type && 0 type * 0 type[1] 0 type(&)[1] 0 type(&&)[1] 0 destruct f2(T&) type 1 type & 0 type && 0 type * 0 type[1] 0 type(&)[1] 0 type(&&)[1] 0 f3(T&&) type 0 type & 1 type && 0 type * 0 type[1] 0 type(&)[1] 0 type(&&)[1] 0 type * f1(T) type 0 type & 0 type && 0 type * 1 type[1] 0 type(&)[1] 0 type(&&)[1] 0 f2(T&) type 0 type & 0 type && 0 type * 1 type[1] 0 type(&)[1] 0 type(&&)[1] 0 f3(T&&) type 0 type & 0 type && 0 type * 0 type[1] 0 type(&)[1] 0 type(&&)[1] 0 type[] construct f1(T) type 0 type & 0 type && 0 type * 1 type[1] 0 type(&)[1] 0 type(&&)[1] 0 f2(T&) type 0 type & 0 type && 0 type * 0 type[1] 1 type(&)[1] 0 type(&&)[1] 0 f3(T&&) type 0 type & 0 type && 0 type * 0 type[1] 0 type(&)[1] 1 type(&&)[1] 0 destruct destruct destruct
sqrt(N)の素因数分解
コード
vector<uint64_t> prime_factor(uint64_t n) { vector<array<uint64_t, 2>> ret; for (uint64_t i = 2; i * i <= n; ++i) { if (n % i) continue; uint64_t cnt = 0; while (n % i == 0) { n /= i; ++cnt; } ret.push_back({ i, cnt }); } if (n != 1) ret.push_back({ n, 1 }); return ret; }
解説のようなもの
for (uint64_t i = 2; i * i <= n; ++i)
まず、ある整数 が合成数の時からなず 以下に約数が存在します。
です。
このfor文では最小の素数である2から、調べる必要のある最大の約数までを探索します。( 以降は対称性により必要ありません)
if (n % i) continue;
ここでは素数以外、またはnの約数に存在しない素数をはじいています。
このif文を通過するiはすべて素数です。なぜでしょうか?
まず、ある合成数 の素因数はすべて 未満です。
iが合成数の時、iで割り切れる はiの素因数をすべて持たなければなりません。
しかしながら、 はiより小さい素数をすべて取り除かれています。
uint64_t cnt = 0;
素数iが の中にいくつ存在するかを保持しておくための変数です。
while (n % i == 0) {
をiで割り切れることができる限りループします。
n /= i; ++cnt;
から素因数iを1つ取り除き、カウント値を1つ増加させます。
ret.push_back({ i, cnt });
素因数iがcnt個存在したことを戻り値用配列に保持させます。
if (n != 1) ret.push_back({ n, 1 });
最後に残った が素数の時、 を戻り値用配列に追加します。