TopCoder Open Qual.1 Hard SequenceMerger
問題概要
与えられた数列の結び(和集合)をソートした数列において、n(n≦10億)番目の項を求めよ。
数列の形式は以下の3つである。
- 等差数列 (初項・公差・項数が与えられる)
- 等比数列 (初項・公比・項数が与えられる)
- 例外 (各項が直接与えられる)
数列を指定する文字列に出現する数字は64bitに入らないこともありうる。
項数がnに満たない場合や、n番目の項が10億より大きい場合は-1を返せ。
解法
数列の数は10億を超えるので、直接書き出していくような解法は取れない。
n番目の項の値を二分法により求めることを考える。
では具体的にn番目の項の値valを求めるにはどうすれば良いかと言うと、
- 和集合数列において、ある値「未満の項の数」を求める関数countを作り
- count(val)≦n<count(val+1)であるようなvalを二分法により探す
countについて。
例外数列の分は直接数えればよい。ソートしておくとlower_boundが使えて楽だが別に時間は余裕があるので必須ではない。
等差数列は、初項からいくつまでがval未満であるか割り算により求める。
等比数列はlogを使えばいくつまでがval未満かわかる。
二番目の条件は具体例を見れば比較的わかりやすい。
7,13,13,15,15,16,17が和集合数列であるとしよう。この数列の5番目の項を求めたいとする。
0番目から数えることにする(そのほうが数えやすいので)。なので以下4番目と呼ぶ。
4番目の項の値は15(の右のほう)である。
ここで、15より小さい項が3個、15以下の項が5個あるという意味である。
count(15)=3≦4(番目)<count(15+1)=5となるようなval=15がn=4番目の値となっていることがわかるだろうか。
基本的な考え方はこれで良いが、数列を指定する文字列において64bitにも収まらない巨大な数が出てきたり(1000000002などと置き換えればよい)、doubleでは小数点誤差でテストに通らない(long doubleを使う必要がある)など細かい落とし穴が非常に多い。
ソースコード
TopCoderのPracticeRoomから見られるし、要らないような気もするけど……
vi e; vector<vi> a,g; class SequenceMerger { public: vector <int> getVal(vector <string> seqs, vector <int> positions) { e.clear(),a.clear(),g.clear(); int n=seqs.size(),m=positions.size(); //parse rep(i,n) { stringstream ss(seqs[i].substr(2)); vi tmp; string str; while(ss>>str) { ll t; if(str.size()>10)t=1000000001; else stringstream(str)>>t; if(t>1000000000)t=1000000001; tmp.pb((int)t); } if(seqs[i][0]=='E')e.insert(e.end(),all(tmp)); else if(seqs[i][0]=='A')a.pb(tmp); else g.pb(tmp); } sort(all(e)); vi ans; rep(i,m) { positions[i]--; int lo=0,hi=1000000002,mid; while(lo+1<hi) { mid=(lo+hi)/2; if(count(mid)>positions[i])hi=mid; else if(count(mid+1)<=positions[i])lo=mid; else { lo=mid; break; } } if(lo>1000000001)lo=1000000001; ans.pb(lo==1000000001?-1:lo); } return ans; } int count(int val) { int ret=lower_bound(all(e),val)-e.begin(); rp(i,a) { if(a[i][1]==0) { if(a[i][0]<val)ret+=a[i][2]; } else if(a[i][0]<=val) { ret+=min(a[i][2], (val-a[i][0])/a[i][1]+((val-a[i][0])%a[i][1]!=0) ); } ret=min(ret,1000000001); } rp(i,g) { if(g[i][1]==1||g[i][0]==0) { if(g[i][0]<val)ret+=g[i][2]; } else if(g[i][0]<=val) { ret+=min(g[i][2], (int)ceil(log(ld(val)/g[i][0])/log(ld(g[i][1]))) ); } ret=min(ret,1000000001); } return ret; } }