ABC 254

D

https://atcoder.jp/contests/abc254/tasks/abc254_d

命題1

$i \times j \text{ が平方数} \leftrightarrow \frac{i\times j}{f(i) \times f(j)} \text{ が平方数}$

($\rightarrow$)

$i \times j$ が平方数であるから $i \times j$ は以下のようにかける。

$$ i \times j = \prod_{k} p^{2r_k}_{k} $$

ここで、$p_k$ は素数であり、$r_k$ は非負整数。 $f(i)$, $f(j)$ も平方数であるから

$$ f(i) = \prod_{k} p^{2n_{k}}_{k} $$$$ f(j) = \prod_{k} p^{2m_{k}}_{k} $$

とかける。ここで $n_k$, $m_k$ は非負整数。

よって

$$ \frac{i\times j}{f(i) \times f(j)} = \prod_{k} p^{2(r_k - n_k - m_k)}_k $$

であるから、因数が偶数となるため平方数。

$(\leftarrow)$

$\frac{i\times j}{f(i) \times f(j)}$ が平方数であるから、$\frac{i\times j}{f(i) \times f(j)} = x^2$ となるある正の整数 $x$ が存在する。 同様にして $f(i) = y^2, f(j) = z^2$ とかける。ここで $y, z$ は正の整数.

$$ i \times j = \frac{i\times j}{f(i) \times f(j)} \times f(i) \times f(j) = x^2 y^2 z^2 = (xyz)^2 $$

よって $i \times j$ は平方数。

命題2

$\frac{i\times j}{f(i) \times f(j)}$ が平方数 $\leftrightarrow$ $\frac{i}{f(i)} = \frac{j}{f(j)}$.

$(\leftarrow)$

$\alpha = \frac{i}{f(i)} = \frac{j}{f(j)}$ とすると $\frac{i\times j}{f(i) \times f(j)} = \alpha^2$ より平方数

$(\rightarrow)$

背理法で証明する。すなわち、 $\frac{i}{f(i)} \neq \frac{j}{f(j)} \rightarrow \frac{i\times j}{f(i) \times f(j)}$ は平方数ではないを示す。

$f(i)$ は $i$ の約数のうち最大の平方数であるから $\frac{i}{f(i)} = \prod_{k} p_k$ とかける(冪がすべて1)。同様にして $\frac{j}{f(j)} = \prod_{l} p_l$ とかけ、 $\frac{i}{f(i)} \neq \frac{j}{f(j)}$ であるから少なくとも1つは異なる素因数を持つ。 よって、 $\frac{i\times j}{f(i) \times f(j)}$ は少なくとも1つは冪が1となる素因数をから平方数ではない。 背理法より $\frac{i\times j}{f(i) \times f(j)}$ が平方数 $\rightarrow$ $\frac{i}{f(i)} = \frac{j}{f(j)}$ が示された。

解説

命題1, 2 より $i/f(i)$ が同じになる数同士をかけると平方数になるから、$i = 1 \sim N$ を $i/f(i)$ の値でグルーピングして、それぞれのグループの大きさの2乗の和を取れば良い.

$O(N\sqrt{N})$ の計算量のプログラムでもとりあえず AC はした

#include <bits/stdc++.h>
#define all(v) (v).begin(), (v).end()
#define rall(v) (v).rbegin(), (v).rend()
#define rep(i, n) for (int i = 0; i < (n); ++i)
#define rep2(i, k, n) for (int i = (k); i < (n); ++i)
using namespace std;
using ll = long long;
// using P = pair<ll,ll>;
using P = pair<int, int>;
using vint = vector<int>;
using vll = vector<ll>;
using vvint = vector<vector<int>>;
using vvll = vector<vector<ll>>;

// const ll INF = (ll)2e18+9;
const int INF = (int)2e9 + 7;
// const ll MOD = (ll)1e9+9;
template <typename T>
void chmin(T& a, T b) {
    a = min(a, b);
}
template <typename T>
void chmax(T& a, T b) {
    a = max(a, b);
}

template <typename T>
void print(vector<T> v) {
    int n = v.size();
    rep(i, n) {
        if (i == 0)
            cout << v[i];
        else
            cout << ' ' << v[i];
    }
    cout << endl;
}

void yesno(bool x) {
    puts(x ? "Yes" : "No");
}

void solve();

int main() {
    solve();
    return 0;
}

void solve() {
    ll N;
    cin >> N;
    vll sq;
    for (ll x = 1; x * x <= N; x++)
        sq.push_back(x * x);

    vll cnt(N + 1, 0);
    rep2(i, 1, N + 1) {
        ll s = 1;
        for (auto x : sq) {
            if (x <= i && i % x == 0)
                s = x;
        }
        cnt[i / s]++;
    }

    ll ans = 0;
    for (auto x : cnt)
        ans += x * x;
    cout << ans << endl;
}

解説コードの解説

https://atcoder.jp/contests/abc254/editorial/4065

解説のコードは約数列挙にエラトステネスを使っているっぽい.

https://qiita.com/drken/items/3beb679e54266f20ab63

vector<vector<int>> d(n+1);d[x]x の約数をすべて入れている配列. この配列の中の数字に平方数があるか調べて最大のものをメモ。最後に cnt[i/f] を increment して数を数えている