ABC161-F: Division or Subtraction
問題: https://atcoder.jp/contests/abc161/tasks/abc161_f
自分は という制約を見ると、前半パート と後半パート に分けて考えることが多くて、今回もそれで解けました。
前半パートについては、 で割り切れる限り割って、割り切れなくなったら で割った余りを判定するだけです。かなり定数倍が小さい で解けます。
後半パートは少し工夫が必要です。まず、がで割り切れる場合は を除いて条件を満たさないことがわかるので除外して考えます( なので)。次がポイントなのですが、 を で割った商を で余りを とすると、 を で割った商が であるならば余りは になります。例えば、となり、余りがちょうど (商の値)ずつ減っていくことがわかるでしょう。この着目より、 を で割った商が のときに余りが となる は高々1つしかなく、 のときは の場合にのみ存在し、 のときは必ず存在する ことがわかります。つまり、後半パートは を増やしつつ、 が同じ場合はまとめて計算することで解けます。 の範囲は となるので、計算量は です。
全体の計算量は となります。以下がコンテスト中の実装ほぼそのままなのですが、簡単のため境界は決め打ちしています。
void solve(istream& cin, ostream& cout) { ll n; cin >> n; ll ans = 0; ll s = 1e+7; for(ll i=2; i<min(s, n); i++) { ll tmp = n; while(tmp%i==0) { tmp/=i; } if (tmp%i == 1) { ans++; } } for(ll i=s; i<n; i++) { ll p = n/i; ll r = n%i; if (r%p == 1) { ans++; } if (p == 2) { // If p=1, K=N-1 satisfies the condition. Skipping. ans++; break; } i = n/p; } // If K=N, the condition is satisified. ans++; cout << ans << endl; }
コンテスト中の提出: https://atcoder.jp/contests/abc161/submissions/11541243
公式解説は頭がいい・・・
上記の実装では後半パートでも を増やしながら進めていましたが、さらに考察を進めると後半パートの実装はさらに簡単になって、 を列挙しながら を で割った余りが かどうか判定するだけでよくなります (ただし、 がvalidかの判定は必要)
void solve(istream& cin, ostream& cout) { ll n; cin >> n; ll ans = 0; ll medk = 0; for(ll k=2; k*k<=n; k++) { ll tmp = n; while(tmp%k==0) { tmp/=k; } if (tmp%k == 1) { ans++; } medk = k; } for(ll p=1; p*p<n; p++) { if (p==1 || n%p == 1) { ll k = (n-1)/p; if (2 <= k && medk < k) { ans++; } } } // K=N ans += 1; cout << ans << endl; }
ここまで考察すると がソースコード上に見えてきて、公式解説に近づいてきている気がします
実際の提出: https://atcoder.jp/contests/abc161/submissions/11558100