TL;DR
- を固定したとき、対応する の個数が で求められる。
- の範囲を調べればよく、手元なら何とか前計算できる。
- 前計算すると、考慮すべき(i.e. 対応する が 1個以上ある) はかなり少ないことが分かるので、それを埋め込めば通る。
問題概要
- 以下の条件をすべて満たす の個数を求めてください。
- は 以下の正整数である。
- ( の整数部分)を とするとき、 は の接頭辞になる。
- () ケース与えられるので、それぞれに答えてください。
考察
実験
例を見ると、 や は条件を満たすことが分かる。 の形で表される は条件を満たすっぽい。 それ以外で条件を満たす にはどんなものがあるか、愚直に実験してみる。 公式解説 とほぼ同様の実験コードだが、 に対応する を出力するようにしてある。
実験コード
#include<bits/stdc++.h> using namespace std; long long sq(long long x){ long long l=0,r=2e9; while(l<=r){ long long te=(l+r)/2; if(te*te <= x){l=te+1;} else{r=te-1;} } return r; } int main(){ for(long long x=1;x<=11111;x++){ long long y=sq(x); string s=to_string(x); string t=to_string(y); bool ok=true; for(int i=0;i<t.size();i++){ if(s[i]!=t[i]){ok=false;} } if(ok){cout << x << " " << y << "\n";} } return 0; }
出力
1 1 80 8 90 9 91 9 ... 99 9 100 10 101 10 ... 109 10 9800 98 9900 99 9901 99 ... 9999 99 10000 100 10001 100 ... 10099 100
出力結果を眺めていると にも法則性がありそう(公式解説はこの法則性を使って解いている)だが、 としてありうる値はかなり限られていそうなことが分かる。 が少ないならば、 から の個数を逆算して速く処理できないだろうか?
y を固定したときの x の個数
ある を固定したとき、題意の条件を満たす ()の個数を とする。
いったん実験結果を忘れて、 を求める方法を考えてみる。
条件を区間に変形する
問題概要で が満たすべき条件は以下のようだった ( という条件もとりあえず無視)。
- ( の整数部分)を とするとき、 は の接頭辞になる。
を固定することで、これが2つの条件で言い換えられることになる。
- は の接頭辞である。
さらに、それぞれの条件を の不等式でこのように書き換えることができる。
- となる非負整数 が存在する。
2つ目の条件の書き換えはやや非自明だが、次のように考えることができる。
例えば のとき1、2つ目の条件を満たす は ( は任意の数字)の形になる。 の部分を で表して、その桁数を とする2と、 となる。 ここで が の部分に影響を及ぼさないようにするためには でなければならず、この不等式を変形すると結局上の式になる。
この形で表せば、 を求める問題は、不等式で表される2つの区間の共通部分を求める問題に帰着できる。 新たな変数 が導入されてしまったが、2つの区間が共通部分を持つのは上の不等式で表される区間の右端と下の不等式で表される区間の左端が を満たすときに限られる。 つまり、 としては 個ぐらいの値を調べればよいだけなので、計算量的には問題ないだろう。 また、区間で表されているので、無視していた という条件を再度取り込むのにも相性がよさそうだ。
前計算・埋め込み
の範囲の について を足し合わせれば答えが求まるが、計算量が1ケースあたり となり、そのまま提出すると間に合わない。 だが、実験結果から、実際にありうる はかなり限られていそうだったので、手元でありうる を前計算しておけば何とか通せないだろうか?
前計算
答えを求めるには となりうる だけを考えればよい。 すなわち、 の条件を考慮せず としても となるのであれば、提出用コードではその を調べる必要はない。
前計算コードでは の範囲を全探索して、 となる を出力している。 提出用コードではこの だけを調べればよい。
前計算コード
#include<bits/stdc++.h> using namespace std; using ll = long long; const ll ymax = 1'000'000'000LL; inline ll div_ceil(ll num, ll den){ // 切り上げ除算 return (num + den - 1) / den; } ll f(ll y){ // y を決め打ちしたときの対応する x の個数 // x <= N の条件は考慮しない // あらかじめ 10^k の上限(区間が共通部分を持つ条件)を求めておく // (オーバーフロー対策) ll b_lim = div_ceil((y + 1) * (y + 1), y); ll ret = 0; for(ll b = 1; b < b_lim; b *= 10){ // b = 10^k. 各 k に対して x の個数を求める ll l = max(y * y, b * y); // 2つの区間の下限をまとめる ll r = min((y + 1) * (y + 1), b * (y + 1)); // 2つの区間の上限をまとめる ret += max(0LL, r - l); } return ret; } int main(){ // 提出用コードに埋め込むべき y を出力する for(ll y = 1; y <= ymax; y++){ if(f(y) > 0) cout << y << ", "; // f(y, ∞) > 0 であれば出力 } cout << endl; return 0; }
出力結果
30秒ぐらいで出てきました(i9-10850K)。
1, 8, 9, 10, 98, 99, 100, 998, 999, 1000, 9998, 9999, 10000, 99998, 99999, 100000, 999998, 999999, 1000000, 9999998, 9999999, 10000000, 99999998, 99999999, 100000000, 999999998, 999999999, 1000000000,
としてありうる値は少ないとは思ってたけど、びっくりするぐらい少なかった。
提出
先ほど求めておいた を埋め込んで提出する。 前計算用コードを少しいじれば提出用コードになる。
提出用コード
#include<bits/stdc++.h> using namespace std; using ll = long long; const ll ymax = 1'000'000'000LL; inline ll div_ceil(ll num, ll den){ // 切り上げ除算 return (num + den - 1) / den; } ll f(ll y, ll N){ // y を決め打ちしたときの対応する x (<= N) の個数 // あらかじめ 10^k の上限(区間が共通部分を持つ条件)を求めておく // (オーバーフロー対策) ll b_lim = div_ceil((y + 1) * (y + 1), y); ll ret = 0; for(ll b = 1; b < b_lim; b *= 10){ // b = 10^k. 各 k に対して x の個数を求める ll l = max(y * y, b * y); // 2つの区間の下限をまとめる ll r = min({(y + 1) * (y + 1), b * (y + 1), N + 1}); // 2つの区間の上限 && x <= N をまとめる ret += max(0LL, r - l); } return ret; } ll solve(ll N){ // 各ケースの問題を解く const vector<ll> ys({1, 8, 9, 10, 98, 99, 100, 998, 999, 1000, 9998, 9999, 10000, 99998, 99999, 100000, 999998, 999999, 1000000, 9999998, 9999999, 10000000, 99999998, 99999999, 100000000, 999999998, 999999999, 1000000000}); // 埋め込み ll ret = 0; for(ll y : ys) ret += f(y, N); return ret; } int main(){ int T; cin >> T; for(int t = 0; t < T; t++){ ll N; cin >> N; cout << solve(N) << endl; } return 0; }
提出結果
AC. はいキン肉マンおめでとう
https://atcoder.jp/contests/arc174/submissions/51430684
あとがき
コンテスト中は の満たす性質を実験→未証明ACしていた人が多かったようだ。 この記事でも手計算での証明は行っていないが、ありうる を全探索して埋め込んでいるので一応の正当性は確保できているものと思いたい……