なんかユーザー解説の数がすごいことになっててビビる!
問題概要
以下の正の整数
の組であって、
が平方数であるようなものの個数を求めよ。
制約
考えたこと
この手の問題では「ある変数を固定して考える」という常套手段がある!!
今回は を固定して考えよう。つまり私たちは次の問題を解決したいということになる。
整数 が与えられる。以下の条件を満たす整数
の個数を求めよ。
は平方数である
である
次に考えるべきことは、「一体どういうときに が平方数になるのだろうか」という部分だ。
が平方数になる
を求める
まずは、 を素因数分解してみよう。
と表せるとする。ここで、 が奇数であるような素因数
たちを改めて
とすると、ある整数
を用いて
と表せることになる。具体例をあげると、1440 を素因数分解すると
となる。これを上のように表すと
となる。つまり、 ということだ。
このとき、 が平方数であるということは、
が平方数であるということと同値になる。
さらに、 が平方数であるということは、
を素因数分解したときに、次の条件と同値である!
は
を素因数に持ち、しかもそれらの指数は奇数である
のそれら以外の素因数については、指数が偶数である
これをさらに分かりやすく言い換えると、次のようになる。 とおくことにする。
は平方数
を用いて、
と表せる
ここまで来ると、実はすでに効率のよい解法が得られている!! それを次にまとめる。
解法まとめ
解法をまとめると、次のようになる。
に対して、
が平方数となる
(
) の個数を次のように求める。
を素因数分解したときに、指数が奇数であるような素因数の積を
とする
- このとき、
が平方数であることは、整数
を用いて
と表せることと同値である
- よって、条件を満たす
の個数は「
以下の平方数の個数」に一致する
最後に計算量を見積もる。各 を素因数分解するのに
の計算量を要することから、全体の計算量は
となる。
なお、高速素因数分解を用いると、 の素因数分解を
の計算量で求めることもできる。その技法を用いると、全体の計算量も
となる。
コード
解法 1:愚直に素因数分解する方法
計算量は となる。提出すると 182ms だった。
#include <bits/stdc++.h> using namespace std; // 素因数分解 vector<pair<long long, long long> > prime_factorize(long long n) { vector<pair<long long, long long> > res; for (long long p = 2; p * p <= n; ++p) { if (n % p != 0) continue; int num = 0; while (n % p == 0) { ++num; n /= p; } res.push_back(make_pair(p, num)); } if (n != 1) res.push_back(make_pair(n, 1)); return res; } int main() { long long N; cin >> N; long long res = 0; for (long long i = 1; i <= N; ++i) { // i を素因数分解して、指数が奇数である素因数の積を求める const auto &pf = prime_factorize(i); long long Q = 1; for (auto [p, e] : pf) if (e & 1) Q *= p; // N/Q 以下の平方数の個数を求める long long sq = (long long)sqrt(N/Q); res += sq; } cout << res << endl; }
解法 2:高速素因数分解を用いる方法
エラトステネスの篩を用いる。 となる。提出すると 33ms だった。
#include <bits/stdc++.h> using namespace std; // isprime[n] := is n prime? // min_factor[n] := the min prime-factor of n struct Eratos { vector<int> primes; vector<bool> isprime; vector<int> min_factor; Eratos(int MAX) : primes(), isprime(MAX+1, true), min_factor(MAX+1, -1) { isprime[0] = isprime[1] = false; min_factor[0] = 0, min_factor[1] = 1; for (int i = 2; i <= MAX; ++i) { if (!isprime[i]) continue; primes.push_back(i); min_factor[i] = i; for (int j = i*2; j <= MAX; j += i) { isprime[j] = false; if (min_factor[j] == -1) min_factor[j] = i; } } } // prime factorization vector<pair<int,int>> prime_factors(int n) { vector<pair<int,int> > res; while (n != 1) { int prime = min_factor[n]; int exp = 0; while (min_factor[n] == prime) { ++exp; n /= prime; } res.push_back(make_pair(prime, exp)); } return res; } }; int main() { long long N; cin >> N; // エラトステネスの篩を準備 Eratos er(N + 1); long long res = 0; for (long long i = 1; i <= N; ++i) { // i を素因数分解して、指数が奇数である素因数の積を求める const auto &pf = er.prime_factors(i); long long Q = 1; for (auto [p, e] : pf) if (e & 1) Q *= p; // N/Q 以下の平方数の個数を求める long long sq = (long long)sqrt(N/Q); res += sq; } cout << res << endl; }