執筆時レート:水色
問題文→https://atcoder.jp/contests/abc301/tasks/abc301_d
公式解説→https://atcoder.jp/contests/abc301/tasks/abc301_d/editorial
目次
問題
0
, 1
, ?
からなる文字列 および整数 が与えられる.
に含まれる?
をそれぞれ0
または1
に置き換えて2進数と見なしたとき, 以下で最大のものを(10進数として)出力せよ.
ただし,そのような 以下の値が存在しなければ,-1
を出力せよ.
制約
- は
0
,1
,?
からなる文字列 - は整数
解説
にある?
の個数を とすると,作られる2進数は 種類になる.
このうち 以下で最大のものを見つけたい.
サンプルケース1で樹形図を書いてみる.
ここで がどこにあるか書き込むと,
以下をOKとすると単調性が見えてきた.二分探索っぽい.
?
を上の桁から見ていって,1にしてもOKのものが存在するなら1にしたら良い.存在しなければ0にする.
「OKのものが存在するか」というのは,単調性により,その桁を1にしたときの最小値がOKかどうかを見れば良い.
つまり,その桁以降の?
をすべて0にした数がN以下かどうかを見れば良い.
?
ではない桁はどう扱うか.
では1
で では0
だとその時点で必ず が上回ってしまう.
その時点で答えが-1
であることは確定する.
他の1
と0
の組のパターンはN以下であることが保たれるので問題ない.
下の実装では途中で処理を打ち切らずに,最終的な答えが 以下であるか,後で評価している.
実装
#include <bits/stdc++.h> using namespace std; #define rep(i,n) for(int i=0; i<n; i++) using ll = long long; int main() { string s; ll n; cin>>s>>n; int len=s.size(); // よく使うので2のべき乗を前計算 // p2[i]=2のlen-i-1乗の値 vector<ll> p2(len); p2[len-1]=1; for(int i=len-2;i>=0;i--) p2[i]=p2[i+1]*2; ll res=0; rep(i,len) { // 上からi桁目を見る if(s[i]=='?') { // この桁を1にして,それ以降の?を0にした値がn以下か? ll val=res; val+=p2[i]; for(int j=i+1;j<len;j++) { if(s[j]=='1') val+=p2[i]; } if(val<=n) { // この桁を1として良い res+=p2[i]; } }else{ if(s[i]=='1') { res+=p2[i]; } } } // 最後にn以下かチェック if(res<=n) cout<< res <<'\n'; else cout<< -1 <<'\n'; return 0; }
楽な実装
公式解説(楽な実装)では,最初に のすべての?
を0
に変えた値が 以下かをチェックしている.
から作られる数の最小値が を上回っていれば,どう作っても 以下になるはずが無いので答えは-1
に確定.
そうでないならば,少なくともその最小値が 以下であるから,上の桁から見ていくアルゴリズムで必ず答えが得られることが保証される.
上の実装でのres
が,よくやる二分探索の実装(ok
とかng
とか使うやつ)でのok
のように常に 以下の値を保持しているイメージがする.
なぜ上の桁から決めることが二分探索なのか?
上の桁から決めることで,表現されうる数の範囲が半分ずつに絞られていくからだ.
ちなみに
今回のABC301では初めて配点が問題に応じて調整された.
今まで:100-200-300-400-500-500-600-600
今回 :100-200-300-400-475-500-600-625
配点と言えば,過去に@kyopro_friendsさんが触れていた.
サーバル「私が配点を決めていいなら 1-2-4-8-16-32-64-128 がいいかなー」
— 競技プログラミングをするフレンズ (@kyopro_friends) 2022年12月21日
2進数表記だと
1-10-100-1000-10000-100000-1000000-10000000
ここでQuestion.
コンテスト終了後の得点が2進数表記で10110101だったら,その人は全体でどの辺りに位置しているだろうか?
二分探索で考えてみてね.