読者です 読者をやめる 読者になる 読者になる

メモ

yukicoderで遊んでいる競プロゆるふわ勢

yukicoder No.413 +5,000,000pts

問題はこちら
No.413 +5,000,000pts - yukicoder

余談
サンプル出力の数がどうみても日付なのだけど、何なのかわからず気になっているので、わかる方がいましたら教えてください

(※以下は試行錯誤により解く過程を記したものです。ちゃんとした解説が読みたい方はeditionalを読んでください)
calc関数は、二次不等式t*t+t≦dを解の公式を用いて解いているので、誤差が発生しなければ正しい値を返すはず。
ということは、バグるのは誤差が発生するとき。
doubleからlong longへの変換のときの丸めが怪しそうだなと思ったので、実数の範囲での最大値がほぼ整数となるようなものであって、誤差が発生しような大きめのdについて調べてみる

#define ll long long
ll calc(ll d) {
  return (ll)((-1 + sqrt(1 + 4*d)) / 2.0);
}
int main(){
	for(ll M=1e8;M>1e8-100;M--)for(int i=-100;i<100;i++){
		//t*t+t<=dの実数の範囲での解の最大値が整数になるのは
		//dがある整数Mを用いてM*M+Mとあらわされるとき(不等式から明らか)
		ll d=M*M+M+i;
		ll ans=calc(d);
		if(ans*ans+ans>d||(ans+1)*(ans+1)+(ans+1)<=d)printf("%lld %d %d\n",M,i);
	}
	return 0;
}

これを実行してみると

100000000 -1 100000000
99999999 -1 99999999
99999998 -1 99999998
中略
99999903 -1 99999903
99999902 -1 99999902
99999901 -1 99999901

という出力が得られた。どうやらdがM*M+M-1という形で書けるときのようだ。
このときt*t+t≦dの実数の範囲での解の最大値はMよりもわずかに小さくなるので、正しい答えはM-1となるはずだが、calc関数はMを返してしまっている。

ということでM*M+M-1のような形で書ける大きな数を10万個生成する

int main(){
	long M;
	for(M=100000000;M>99900000;M--)printf("%ld\n",M*M+M-1);
	return 0;
}

これでめでたくACを得たが、解説を読むと、どうやら問題はdoubleからlong longへの変換ではなく、sqrt関数へ渡すためのlong longからdoubleの変換の方だったらしい。
doubleの精度は53bitなので、64bit整数の値を保持できるとは限らないことが問題の本質だったそうだ。

それはさておき短縮。
とくに工夫する要素はない

i=999e5;main(){for(;i++<1e8;)printf("%ld\n",~-i+1L*i*i);}

57B