質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.40%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

4回答

853閲覧

なぜiの条件式がこうであるとうまくいくのか

SUPERFIRE

総合スコア5

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

1クリップ

投稿2024/07/09 09:41

編集2024/07/14 13:50

理解したいこと

自分の書いたコードの中でi<25という条件式が出てくるのですが、なぜ25未満の数だとうまくいかないのかを理解したいです。

前提

「競技プログラミングの鉄則」という本を使って学習していて、B12の問題を解いていました。i < 10000などと大きな値をとってACしたのですが、i < xのxの部分をコードが機能する最小の値にしたいと考えいじっていたら、x >= 25で機能することが分かりました。しかし、なぜx >= 25で機能するのか分からなかったので、質問したいと考えました。

以下B12の問題です。

問題文---
正の整数 N が与えられます。
x^3+x=N を満たす正の実数
x を出力してください。ただし、相対誤差または絶対誤差が 0.001 以下であれば正解とします。

制約
1≤N≤100000
N は整数

入力
入力は以下の形式で標準入力から与えられます。

N

※「競技プログラミングの鉄則 米田優峻[著]」問題B12 から引用

問題:https://atcoder.jp/contests/tessoku-book/tasks/tessoku_book_ck
解答コード:https://github.com/E869120/kyopro-tessoku/blob/main/editorial/chap03/cpp/answer_B12.cpp

--

該当のソースコード

C++

1#include <bits/stdc++.h> 2using namespace std; 3 4double N, m; 5int main() { 6 cin >> N; 7 double L = 0, R = 100; 8 for (int i = 0; i < 25; i++) { 9 m = (L + R) / 2; 10 if (N < (m * m * m + m)) R = m; 11 else L = m; 12 } 13 cout << m << endl; 14} 15

環境

環境は以下のものを使用しています。
https://atcoder.jp/contests/tessoku-book/custom_test

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

i < xのxの部分をコードが機能する最小の値にしたい

そもそも目指すべきものが違うのではないでしょうか。「相対誤差または絶対誤差が 0.001 以下」かどうかそのものを検証するのが適当ではないかと考えます。

投稿2024/07/09 09:57

maisumakun

総合スコア145628

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

SUPERFIRE

2024/07/11 13:59

ACの基準について再認識するきっかけになりました。ご回答ありがとうございます。
guest

0

ベストアンサー

i < xのxの部分をコードが機能する最小の値にしたい

「コードが機能する」かどうかを「以下の環境で標準出力に表示される値が変わらない」かどうかで判断していないでしょうか。

環境は以下のものを使用しています。
https://atcoder.jp/contests/tessoku-book/custom_test

こちらの環境だと有効数字6桁までしか表示されませんが、実際の値はもっと下の桁まで数字が続いています。例えば、表示部分を以下のように変更すると、有効数字8桁まで表示されます。

c++

1 cout << setprecision(8) << m << endl;

こちらで試すと、「x >= 25で機能する」というのも変わってくると思います。

追記

ご提示のコードの場合、初期状態で入り込む可能性のある絶対誤差の最大値は100で、ループを1回回すごとにその最大値が半分になります。ループ17回目で0.00076...となり0.001を下回るので、17回やれば確実に問題の条件を満たせます。ただし、ここで求めた値はあくまで絶対誤差の最大値ですし、問題では相対誤差0.001も許容するので、実際にはループ16回で題意を満たす回答となります(以下のコードで確認)。

c++

1#include <bits/stdc++.h> 2using namespace std; 3 4double f(double m) { 5 return m * m * m + m; 6} 7 8double inv(double N, int x) { 9 double L = 0, R = 100, m = 0; 10 for (int i = 0; i < x; i++) { 11 m = (L + R) / 2; 12 if (N < f(m)) R = m; 13 else L = m; 14 } 15 return m; 16} 17 18bool valid(int x) { 19 for (int N = 1; N <= 100000; ++N) { 20 double m = inv(N, x); 21 if (!(f(m - 0.001) <= N && N <= f(m + 0.001) || f(m / 1.001) <= N && N <= f(m / 0.999))) 22 return false; 23 } 24 return true; 25} 26 27int main() { 28 int ok = 50, ng = 0; 29 while (ok - ng > 1) { 30 int mid = ng + (ok - ng) / 2; 31 if (valid(mid)) ok = mid; 32 else ng = mid; 33 } 34 cout << ok << endl; 35}

投稿2024/07/09 11:10

編集2024/07/09 21:09
actorbug

総合スコア2325

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

SUPERFIRE

2024/07/11 13:57

1つの問題で多くのことを学ぶことができました。最後まで丁寧に教えてくださり、ありがとうございました。
guest

0

回答ではありません

このまま終わると最後まで理解されないままになりそうですね。

f(x) = x^3 + x

という式はdf/dx = 3x^2 + 1なので常に正です。単調増加であることが分かります。
なので、L≦x≦Rの範囲に、f(x)=Nを満たすxを探すには、f(L)≦N≦f(R)となるL,Rを1つ探してその後二分探索でL,Rの範囲を絞ればいいということになります。
その際、単調増加である以上、絞った範囲では誤差が単調減少していきます。

そこまでわかっていれば、L,M,Rの誤差を計算したときの最も低い誤差がその試行回数での一番正解に近いxなので、それが相対誤差または絶対誤差が 0.001 以下になったら探索を終了することで、最短で正解を得ることが出来ます。

あとは初期値を考えるわけですが、x>0のときf(x)>xなので単純に整数を仮定し、Lは0、R=N(適当)が妥当と分かります。

問題と解法が分かったので、コードにすると、

C++

1#include <bits/stdc++.h> 2using namespace std; 3 4double f(double x) { 5 return x * x * x + x; 6} 7 8double binsearch(int N, int& count) { 9 count = 1; 10 double L = 0, R = N; 11 while (R - L > 0.001 && (L>0 ? (R-L)/L : 1) > 0.001) { 12 double M = (L + R) / 2; 13 double fm = f(M); 14 if (fm >= N) R = M; 15 else L = M; 16 ++count; 17 } 18 if (N - f(L) < f(R) - N) return L; 19 else return R; 20} 21 22int main() { 23 int N, count; 24 cin >> N; 25 auto result = binsearch(N, count); 26 cout << result << endl; 27 cerr << "count: " << count << ", f(x) = " << f(result) << endl; 28 return 0; 29}

投稿2024/07/11 15:31

編集2024/07/11 18:02
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

SUPERFIRE

2024/07/14 13:46

貴重なご意見ありがとうございます。内容を拝見させていただいた上で、お聞きしたいことが2つございます。 ①私の理解がなぜ完璧でないのか。どこで間違っているのか。 ②dameoさんの言う、「最短で正解を得る」というのはどういった意味で最短なのか。 以上2点について詳しく説明してくださるとありがたいです。
退会済みユーザー

退会済みユーザー

2024/07/14 14:29

①今聞いているところです ②処理手順が最短ということです
guest

0

maisumakunさん、actorbugさんご指摘ありがとうございました。自分自身が混乱していました。確かに「コードが機能する」かどうかを「以下の環境で標準出力に表示される値が変わらない」かどうかで判断していて、この問題を解くうえでACとなる基準を見失っていました。相対誤差または絶対誤差が 0.001 以下であれば正解となるので、2の累乗では14乗で10000を超えるので、i < 14でもACとなるのですね。

C++

1#include <bits/stdc++.h> 2using namespace std; 3 4double N, m; 5int main() { 6 cin >> N; 7 double L = 0, R = 100; 8 for (int i = 0; i < 14; i++) { 9 m = (L + R) / 2; 10 if (N < (m * m * m + m)) R = m; 11 else L = m; 12 } 13 cout << m << endl; 14}

投稿2024/07/09 13:06

SUPERFIRE

総合スコア5

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

actorbug

2024/07/09 13:45

i < 14 の場合、用意されているテストケースは通るようですが、入力に「1」を与えた場合に絶対誤差・相対誤差ともに0.001を超えます。正しい値は 0.6823... ですが、i < 14 だと 0.6774... ですし、 i < 15 でも 0.6805... なので条件を満たしません。i < 16 で 0.6820... となり、ようやく条件を満たすことができます。
SUPERFIRE

2024/07/10 14:12

返信ありがとうございます。actorbugさんの回答の追記を拝見しました。ACになったので、てっきり安心してしまっていましたが、確かによくよく考えてみたら、2の累乗では14乗で10000を超えるけど、それだと小数点第三位までの精度で計算できず(0.001が100になるには100000個必要で最小の目盛りが0.001にならなければならないので)、絶対誤差が0.001以下に収まりませんね。そして、相対誤差のことをすっかり意識していませんでした。actorbugさんは絶対誤差・相対誤差の問題を二分探索法を利用して検出し、i<16で条件を満たすと考えられたのですね。しかし、私には2つの疑問が残ります。それを以下に示します。 ①i < 16のときどんな場合でも絶対誤差・相対誤差も0.001以下になると言えるのか。 ②競技プログラミングという視点で見たとき、どのように解くのが得策なのか(わざわざforループの回数を算出するのは面倒だから、または、①のようなことを競技時間内に証明することは難しいので、確実に大丈夫な大きな値をとるという手法で解くのがよいのか)。 この2点についてお答えいただけるとありがたいです。また、私の発言に間違っていることや、気になることがありましたらその点についてもご指摘いただけると幸いです。
actorbug

2024/07/10 21:43 編集

> ①i < 16のときどんな場合でも絶対誤差・相対誤差も0.001以下になると言えるのか。 「どんな場合でも」成り立つとは言っていません。問題の制約「1≤N≤100000」「Nは整数」の範疇で成り立つという主張です。 上記コメントについては、「入力に「1」を与えた場合」の話しかしていませんし、私の回答の追記については、問題の制約「1≤N≤100000」「Nは整数」を満たすすべてのNで検証しています。(だから「題意を満たす回答」という言い方をしている) そもそも、L = 0, R = 100で初期化しているのだから、結果がこの範囲に収まらない入力だった場合(-1など)には、正しい出力になりません。 > ②競技プログラミングという視点で見たとき、どのように解くのが得策なのか(わざわざforループの回数を算出するのは面倒だから、または、①のようなことを競技時間内に証明することは難しいので、確実に大丈夫な大きな値をとるという手法で解くのがよいのか)。 競技プログラミングという視点で見るなら、問題の制約を満たす入力に対して時間内に正しい出力をするコードを作れればよいので、わざわざ最小のループ回数を求める必要はありません。証明する時間がもったいないので、確実に大丈夫な大きな値を使うのが望ましいでしょう。(その大きな値で時間超過にならないのであれば)
SUPERFIRE

2024/07/11 13:55

返信ありがとうございます。2つの質問に丁寧にご回答してくださり、ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.40%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問