#ifndef HIDDEN_IN_VS // 折りたたみ用 // 警告の抑制 #define _CRT_SECURE_NO_WARNINGS // ライブラリの読み込み #include using namespace std; // 型名の短縮 using ll = long long; // -2^63 ~ 2^63 = 9 * 10^18(int は -2^31 ~ 2^31 = 2 * 10^9) using pii = pair; using pll = pair; using pil = pair; using pli = pair; using vi = vector; using vvi = vector; using vvvi = vector; using vl = vector; using vvl = vector; using vvvl = vector; using vb = vector; using vvb = vector; using vvvb = vector; using vc = vector; using vvc = vector; using vvvc = vector; using vd = vector; using vvd = vector; using vvvd = vector; template using priority_queue_rev = priority_queue, greater>; using Graph = vvi; // 定数の定義 const double PI = acos(-1); const vi DX = { 1, 0, -1, 0 }; // 4 近傍(下,右,上,左) const vi DY = { 0, 1, 0, -1 }; int INF = 1001001001; ll INFL = 4004004004004004004LL; double EPS = 1e-12; // 入出力高速化 struct fast_io { fast_io() { cin.tie(nullptr); ios::sync_with_stdio(false); cout << fixed << setprecision(18); } } fastIOtmp; // 汎用マクロの定義 #define all(a) (a).begin(), (a).end() #define sz(x) ((int)(x).size()) #define lbpos(a, x) (int)distance((a).begin(), std::lower_bound(all(a), x)) #define ubpos(a, x) (int)distance((a).begin(), std::upper_bound(all(a), x)) #define Yes(b) {cout << ((b) ? "Yes\n" : "No\n");} #define rep(i, n) for(int i = 0, i##_len = int(n); i < i##_len; ++i) // 0 から n-1 まで昇順 #define repi(i, s, t) for(int i = int(s), i##_end = int(t); i <= i##_end; ++i) // s から t まで昇順 #define repir(i, s, t) for(int i = int(s), i##_end = int(t); i >= i##_end; --i) // s から t まで降順 #define repe(v, a) for(const auto& v : (a)) // a の全要素(変更不可能) #define repea(v, a) for(auto& v : (a)) // a の全要素(変更可能) #define repb(set, d) for(int set = 0; set < (1 << int(d)); ++set) // d ビット全探索(昇順) #define repp(a) sort(all(a)); for(bool a##_perm = true; a##_perm; a##_perm = next_permutation(all(a))) // a の順列全て(昇順) #define smod(n, m) ((((n) % (m)) + (m)) % (m)) // 非負mod #define uniq(a) {sort(all(a)); (a).erase(unique(all(a)), (a).end());} // 重複除去 #define EXIT(a) {cout << (a) << endl; exit(0);} // 強制終了 // 汎用関数の定義 template inline ll pow(T n, int k) { ll v = 1; rep(i, k) v *= n; return v; } template inline bool chmax(T& M, const T& x) { if (M < x) { M = x; return true; } return false; } // 最大値を更新(更新されたら true を返す) template inline bool chmin(T& m, const T& x) { if (m > x) { m = x; return true; } return false; } // 最小値を更新(更新されたら true を返す) // 演算子オーバーロード template inline istream& operator>>(istream& is, pair& p) { is >> p.first >> p.second; return is; } template inline istream& operator>>(istream& is, vector& v) { repea(x, v) is >> x; return is; } template inline vector& operator--(vector& v) { repea(x, v) --x; return v; } template inline vector& operator++(vector& v) { repea(x, v) ++x; return v; } // 手元環境(Visual Studio) #ifdef _MSC_VER #include "local.hpp" // 提出用(gcc) #else inline int popcount(int n) { return __builtin_popcount(n); } inline int popcount(ll n) { return __builtin_popcountll(n); } inline int lsb(int n) { return n != 0 ? __builtin_ctz(n) : -1; } inline int lsb(ll n) { return n != 0 ? __builtin_ctzll(n) : -1; } inline int msb(int n) { return n != 0 ? (31 - __builtin_clz(n)) : -1; } inline int msb(ll n) { return n != 0 ? (63 - __builtin_clzll(n)) : -1; } #define gcd __gcd #define dump(...) #define dumpel(v) #define dump_list(v) #define input_from_file(f) #define output_to_file(f) #define Assert(b) { if (!(b)) while (1) cout << "OLE"; } #endif #endif // 折りたたみ用 //--------------AtCoder 専用-------------- #include using namespace atcoder; using mint = modint1000000007; //using mint = modint998244353; //using mint = modint; // mint::set_mod(m); istream& operator>>(istream& is, mint& x) { ll x_; is >> x_; x = x_; return is; } ostream& operator<<(ostream& os, const mint& x) { os << x.val(); return os; } using vm = vector; using vvm = vector; using vvvm = vector; //---------------------------------------- // O(n^3) mint naive(ll n) { mint res = 0; repi(a, 1, n) repi(b, 1, n) repi(c, 1, n) { if (b % a == 0 || a % b == 0 || c % b != 0 || b == c) continue; res++; } return res; } void zikken() { int N = 100; vm res(N + 1); repi(n, 1, N) { res[n] = naive(n); } dump_list(res); exit(0); } /* {0, 0, 0, 0, 1, 2, 7, 10, 18, 27, 41, 49, 76, 88, 112, 142, 178, 197, 249, 272, 334, 385, 434, 465, 567, 620, 682, 753, 854, 900, 1044, 1096, 1214, 1310, 1400, 1508, 1709, 1778, 1883, 2002, 2214, 2293, 2518, 2603, 2787, 2993, 3130, 3225, 3544, 3683, 3904, 4076, 4305, 4419, 4724, 4924, 5253, 5453, 5639, 5771, 6278, 6420, 6622, 6939, 7278, 7527, 7924, 8085, 8405, 8658, 9099, 9272, 9904, 10087, 10342, 10732, 11100, 11413, 11898, 12099, 12720, 13094, 13386, 13600, 14365, 14717, 15027, 15367, 15940, 16176, 17015, 17405, 17872, 18244, 18590, 18990, 19878, 20146, 20663, 21220, 21959} */ //【素数の列挙】O(n log(log n)) /* * n 以下の素数を列挙し,ps に昇順に格納する. */ void eratosthenes(int n, vi& ps) { // verify : https://algo-method.com/tasks/330 ps.clear(); // 素数かどうかを記録しておくためのテーブル vb is_prime(n + 1, true); is_prime[0] = is_prime[1] = false; int i = 2; // √n 以下の i の処理 for (; i <= n / i; i++) { if (is_prime[i]) { ps.push_back(i); for (int j = i * i; j <= n; j += i) is_prime[j] = false; } } // √n より大きい i の処理 for (; i <= n; i++) if (is_prime[i]) ps.push_back(i); } //【約数変換,LCM 畳込み】 /* * Divisor_transform(int n) : O(n log(log n)) * n までの素数を持って初期化する. * * divisor_zeta(vT& a) : O(n log(log n)) * A[j] = Σ_(i | j) a[i] なる A に上書きする. * (約数ゼータ変換,倍数への累積和) * * divisor_mobius(vT& A) : O(n log(log n)) * A[j] = Σ_(i | j) a[i] なる a に上書きする. * (約数メビウス変換,約数への差分) * * vT lcm_convolution(vT a, vT b) : O(n log(log n)) * c[k] = Σ_(lcm(i, j) = k) a[i] b[j] なる c を返す. * ただし c[n] を含めそれ以降は切り捨てる. * * 制約:1-indexed とし,a[0], b[0] は使用しない. * * 利用:【素数の列挙】 */ template struct Divisor_transform { // 参考 : https://qiita.com/convexineq/items/afc84dfb9ee4ec4a67d5 // verify : https://judge.yosupo.jp/problem/lcm_convolution vi ps; // 素数のリスト Divisor_transform() {} Divisor_transform(int n) { eratosthenes(n, ps); } void divisor_zeta(vector& f) { // 具体例: // A[1] = a[1] // A[2] = a[1] + a[2] // A[3] = a[1] + a[3] // A[4] = a[1] + a[2] + a[4] // A[5] = a[1] + a[5] // A[6] = a[1] + a[2] + a[3] + a[6] // A[7] = a[1] + a[7] // A[8] = a[1] + a[2] + a[4] + a[8] int n = sz(f); // 各素因数ごとに下からの累積和をとる repe(p, ps) { repi(i, 1, (n - 1) / p) f[p * i] += f[i]; } } void divisor_mobius(vector& f) { int n = sz(f); // 各素因数ごとに上からの差分をとる repe(p, ps) { repir(i, (n - 1) / p, 1) f[p * i] -= f[i]; } } vector lcm_convolution(vector a, vector b) { int n = sz(a); // 各素因数の max をとったものが lcm なので max 畳込みを行う. divisor_zeta(a); divisor_zeta(b); rep(i, n) a[i] *= b[i]; divisor_mobius(a); return a; } }; //【約数関数 σ_k(n)】O(n log(log n)) /* * i = [1..n] について約数関数 σ_k(i) = (i の約数の k 乗和) を s[i] に格納する. * 特に k = 0 なら約数の個数,k = 1 なら約数の総和と等価である. * * 利用:【約数変換,LCM 畳込み】 */ template void divisor_sigma(int k, int n, vector& s) { // 参考 : https://maspypy.com/%E6%95%B0%E5%AD%A6-%E7%95%B3%E3%81%BF%E8%BE%BC%E3%81%BF%E5%85%A5%E9%96%80%EF%BC%9Adirichlet%E7%A9%8D%E3%81%A8%E3%82%BC%E3%83%BC%E3%82%BF%E5%A4%89%E6%8F%9B%E3%83%BB%E3%83%A1%E3%83%93%E3%82%A6 // verify : https://atcoder.jp/contests/arc068/tasks/arc068_c s.resize(n + 1); s[0] = 0; repi(i, 1, n) s[i] = T(pow(i, k)); Divisor_transform dt(n); dt.divisor_zeta(s); } // O(n log(log n)) mint TLE(ll n) { vm s; divisor_sigma(0, n, s); mint res = 0; repi(b, 1, n) { res += mint(n / b - 1) * (n - (n / b) + 1 - s[b]); } return res; } //【商列挙】O(√n) /* * i=[1..n] に対し,n/i の商が q となる i の範囲が [i1..i2) であることを * {q, i1, i2} として q について降順に qi に格納する. * 各範囲においては余りは公差 -q の等差数列を成す. */ void quotient_range(ll n, vector>& qi) { // verify : https://atcoder.jp/contests/abc230/tasks/abc230_e //【方法】 // n/i の商が q となるような i の範囲を考える.条件を i について整理すると // q = floor(n / i) // ⇔ q <= n / i < q + 1 // ⇔ i q <= n < i(q + 1) // ⇔ n / (q + 1) < i <= n / q // となる. // // この幅が 1 以下であれば,q に対応する i は高々 1 個である.その条件は // n / q - n / (q + 1) <= 1 // ⇔ (q + 1)n - q n <= q(q + 1) // ⇔ n <= q(q + 1) // である.条件をやや弱めて // n <= q^2 // ⇔ √n <= q // としてもオーダーに影響はない. //(例) // 例えば n = 15 のときは以下のように分類できる: // 商 n/i i の範囲 余り n%i // 15 [1..2) [0] // 7 [2..3) [1] // 5 [3..4) [0] // 3 [4..6) [3, 0] // 2 [6..8) [3, 1] // 1 [8..16) [7, 6, 5, 4, 3, 2, 1, 0] ll m = (ll)(sqrt(n) + EPS); // q に対応する i が高々 1 個の部分は i ごとに愚直に考える. for (int i = 1; n / i > m; i++) { qi.push_back({ n / i, i, i + 1 }); } // そうでない部分は q ごとにまとめて考える. repir(q, m, 1) { ll i0 = n / (q + 1LL) + 1; ll i1 = n / q + 1; qi.push_back({ q, i0, i1 }); } } void TLE2() { ll n0; cin >> n0; vector> qis; quotient_range(n0, qis); int m0 = (int)(sqrt(n0) + EPS); vm s_small; divisor_sigma(0, (int)1e6, s_small); // inv[i] : i の逆数 vm inv(msb(n0) + 10); repi(i, 1, sz(inv) - 1) inv[i] = mint(i).inv(); // 1 と素数の昇順リスト vl ps{ 1 }; // cnt0_p[v] : [2..v] 内の p 以下の素数で篩い終えた後残っている数の個数 // cnt1_p[v] : [2..n/v] 内の p 以下の素数で篩い終えた後残っている数の個数 vl cnt0(m0 + 1), cnt1(m0 + 1); repi(v, 1, m0) { cnt0[v] = v - 1; cnt1[v] = n0 / v - 1; } repi(p, 2, m0) { ll c = cnt0[p - 1]; // p が素数でなければ次の p へ if (cnt0[p] == c) continue; ps.push_back(p); // cnt1 の更新 repi(v, 1, m0) { // p^2 > n/v なら更新不要 if (p > n0 / v / p) break; if (v <= m0 / p) { cnt1[v] -= cnt1[v * p] - c; } else { cnt1[v] -= cnt0[n0 / v / p] - c; } } // cnt0 の更新 repir(v, m0, 1) { // p^2 > v なら更新不要 if (p > v / p) break; cnt0[v] -= cnt0[v / p] - c; } } mint res = 0; mint s1 = 0; repe(qi, qis) { ll q, i1, i2; tie(q, i1, i2) = qi; if (i2 - i1 == 1) { res += mint(q - 1) * (n0 - q + 1 - s_small[i1]); s1 += s_small[i1]; // dump(q, i1, i2, s1); continue; } ll n = i2 - 1; int m = (int)(sqrt(n) + EPS); mint s2 = 1; // s : 注目頂点, i_gpf : s の最大素因数が何番目の素数か, sg : σ_0(s), c : s の最大素因数の指数 function dfs = [&](ll s, int i_gpf, mint sg, int c) { // dump("dfs:", s, ps[i_gpf], sg, c); ll p = ps[i_gpf]; // s の最小の子 s * p からの寄与を加算する. if (s != 1) s2 += sg * inv[c + 1] * (c + 2); // その他の s の子からの寄与をまとめて加算する. if (n / s <= m0) s2 += sg * ((2 * cnt0[n / s]) - (2 * cnt0[p])); else s2 += sg * ((2 * cnt1[n0 / n * s]) - (2 * cnt0[p])); // s の最小の子 s * p を探索する. if (s != 1 && s <= n / (p * p)) dfs(s * p, i_gpf, sg * inv[c + 1] * (c + 2), c + 1); // その他の s の子を探索する. for (int i = i_gpf + 1; i < sz(ps) && s <= n / (ps[i] * ps[i]); i++) { dfs(s * ps[i], i, sg * 2, 1); } }; dfs(1, 0, 1, 0); res += mint(q - 1) * (mint(n0 - q + 1) * (i2 - i1) - (s2 - s1)); // dump(q, i1, i2, s1, s2); s1 = s2; } cout << res << endl; } void zikken2() { int N = 20; vm res(N + 1); repi(n, 1, N) { repi(a, 1, n) repi(b, 1, n) repi(c, 1, n) { if (b % a == 0 && c % b == 0) res[n]++; } } dump_list(res); exit(0); } /* {0, 1, 4, 7, 13, 16, 25, 28, 38, 44, 53, 56, 74, 77, 86, 95, 110, 113, 131, 134, 152} http://oeis.org/A061201 To compute a(n) for huge n (see A180365) in sublinear use a(n) = 3*Sum_{i=1..n3} A006218(n/i) - Sum_{j=1..n3} floor(n/(i*j)) + n3^3, where n3 = floor(n^(1/3)). - Andrew Lelechenko, Apr 15 2011 http://oeis.org/A006218 a(n) = Sum_{k=1..n} floor(n/k); also Sum_{k=1..n} d(k), where d = number of divisors (A000005); also number of solutions to x*y = z with 1 <= x,y,z <= n. a(n) = 2*(Sum_{i=1..floor(sqrt(n))} floor(n/i)) - floor(sqrt(n))^2. - Benoit Cloitre, May 12 2002 */ //【長さ 2 の倍数鎖の数え上げ】O(√n) /* * 1 <= x | y <= n を満たす組 (x, y) の個数を返す. * * 利用:【商列挙】 */ template T count_multiple_2chain(ll n) { // 参考:http://oeis.org/A006218 //【方法】 // x を固定すれば,条件を満たす y は n 以下の x の倍数全てなので,その個数は floor(n/x) である. // よって求めるべき値は // Σx∈[1..n] floor(n/x) // である. // これは floor(n/x) の値が等しいところをまとめて計算することにより高速化できる. vector> qis; quotient_range(n, qis); T res = 0; repe(qi, qis) { ll q, i1, i2; tie(q, i1, i2) = qi; res += q * T(i2 - i1); } return res; } void check_count_multiple_2chain() { int N = 20; repi(n, 1, N) { ll res1 = 0; repi(a, 1, n) repi(b, 1, n) { if (b % a == 0) res1++; } ll res2 = count_multiple_2chain(n); dump(n, res1, res2); } exit(0); } /* 1 1 1 2 3 3 3 5 5 4 8 8 5 10 10 6 14 14 7 16 16 8 20 20 9 23 23 10 27 27 11 29 29 12 35 35 13 37 37 14 41 41 15 45 45 16 50 50 17 52 52 18 58 58 19 60 60 20 66 66 */ //【めぐる式二分探索】O(log|ok - ng|) /* * 条件 okQ() を満たす要素 ok と満たさない要素 ng との境界を二分探索する. * 境界に隣り合うような条件を満たす要素(ok 側)の位置を返す. */ template T meguru_search(T ok, T ng, function& okQ) { // 参考 : https://twitter.com/meguru_comp/status/697008509376835584 // verify : https://onlinejudge.u-aizu.ac.jp/courses/lesson/1/ALDS1/all/ALDS1_4_D // 境界が決定するまで while (abs(ok - ng) > 1) { // 区間の中間 T mid = (ok + ng) / 2; // 中間が OK かどうかに応じて区間を縮小する. if (okQ(mid)) ok = mid; else ng = mid; } return ok; /* okQ の定義の雛形 function okQ = [&](ll x) { return true || false; }; */ } //【整数累乗根】O(n log a) /* * 非負の数 a の n 乗根(a^(1/n))の切り捨て値を返す. * * 利用:【めぐる式二分探索】 */ ll integer_root(ll a, int n) { // verify : https://atcoder.jp/contests/abc166/tasks/abc166_d if (a <= 1 || n == 1) return a; // x^k を返す.ただし a を超えた場合は a + 1 を返す. auto pow = [&](ll x, int k) { ll v = 1; rep(i, k) { if (v > a / x) return a + 1; v *= x; } return v; }; // x^n <= a かを返す. function okQ = [&](ll x) { return pow(x, n) <= a; }; ll res = meguru_search(1LL, a + 1, okQ); return res; } //【長さ 3 の倍数鎖の数え上げ】O(n^(2/3)) ? /* * 1 <= x | y | z <= n を満たす組 (x, y, z) の個数を返す. * * 利用:【整数累乗根】,【長さ 2 の倍数鎖の数え上げ】 */ template T count_multiple_3chain(ll n) { // 参考:http://oeis.org/A061201 int m = (int)integer_root(n, 3); T res = T(m) * m * m; repi(i, 1, m) { res += 3 * count_multiple_2chain(n / i); repi(j, 1, m) { res -= 3 * (n / ((ll)i * j)); } } return res; } void check_count_multiple_3chain() { int N = 20; repi(n, 1, N) { ll res1 = 0; repi(a, 1, n) repi(b, 1, n) repi(c, 1, n) { if (b % a == 0 && c % b == 0) res1++; } ll res2 = count_multiple_3chain(n); dump(n, res1, res2); } exit(0); } /* 1 1 1 2 4 4 3 7 7 4 13 13 5 16 16 6 25 25 7 28 28 8 38 38 9 44 44 10 53 53 11 56 56 12 74 74 13 77 77 14 86 86 15 95 95 16 110 110 17 113 113 18 131 131 19 134 134 20 152 152 */ template T count_multiple_2Vchain(ll n) { vector> qis; quotient_range(n, qis); T res = 0; repe(qi, qis) { ll q, i1, i2; tie(q, i1, i2) = qi; res += T(q) * T(q) * T(i2 - i1); } return res; } void check_count_multiple_2Vchain() { int N = 20; repi(n, 1, N) { ll res1 = 0; repi(a, 1, n) repi(b, 1, n) repi(c, 1, n) { if (a % b == 0 && c % b == 0) res1++; } ll res2 = count_multiple_2Vchain(n); dump(n, res1, res2); } exit(0); } /* 1 1 1 2 5 5 3 11 11 4 22 22 5 32 32 6 52 52 7 66 66 8 92 92 9 115 115 10 147 147 11 169 169 12 219 219 13 245 245 14 289 289 15 333 333 16 390 390 17 424 424 18 496 496 19 534 534 20 612 612 */ int main() { // input_from_file("input.txt"); // output_to_file("output.txt"); ll n; cin >> n; mint c2 = count_multiple_2chain(n); mint c2V = count_multiple_2Vchain(n); mint c3 = count_multiple_3chain(n); mint cnt1111 = n; mint cnt1110 = c2 - cnt1111; mint cnt1011 = cnt1110; mint cnt0111 = cnt1110; mint cnt0011 = mint(n) * n - (cnt1111 + cnt1011 + cnt0111); mint cnt0110 = c2V - (cnt1111 + cnt0111+ cnt1110); mint cnt1010 = c3 - (cnt1111 + cnt1011 + cnt1110); mint cnt0010 = n * c2 - (cnt1111 + cnt1110 + cnt1011 + cnt0111 + cnt0011 + cnt0110 + cnt1010); cout << cnt0010 << endl; }