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

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

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

C++11は2011年に容認されたC++のISO標準です。以前のC++03に代わるもので、中枢の言語の変更・修正、標準ライブラリの拡張・改善を加えたものです。

GCC

GCCはGNU Compiler Collectionの略です。LinuxのC言語コンパイラのデファクトスタンダードであり、数多くの他言語やプラットフォームサポートもします。

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

C++

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

Q&A

解決済

1回答

1008閲覧

Atcoder beginner contest 222 E問題の深さ全探索で求めた最短経路の復元が正しくできない

rdld036

総合スコア16

C++11

C++11は2011年に容認されたC++のISO標準です。以前のC++03に代わるもので、中枢の言語の変更・修正、標準ライブラリの拡張・改善を加えたものです。

GCC

GCCはGNU Compiler Collectionの略です。LinuxのC言語コンパイラのデファクトスタンダードであり、数多くの他言語やプラットフォームサポートもします。

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

C++

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

0グッド

0クリップ

投稿2021/10/10 06:16

編集2021/10/10 06:29

前提・実現したいこと

ABC222のE問題を以下のコードで解いたのですが、すべてACになりません。また、自分の実行環境とAtcoderのコードテストで動かした結果が同じサンプル入力を使っているにもかかわらず、一致しません。汚いコードで申し訳ないのですが、原因を教えていただけると幸いです。

該当のソースコード

C++

1#include <bits/stdc++.h> 2using namespace std; 3typedef long long ll; 4typedef long double ld; 5const int INF = 1e9; 6 7template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return 1; } return 0; } 8template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return 1; } return 0; } 9template <typename Type> inline string toString(const Type &a){ostringstream oss; oss << a; return oss.str();} 10 11int N, M, K; 12const int mod = 998244353; 13vector<int> A, C, prev_v; 14vector<vector<pair<int, int>>> G; 15vector<vector<ll>> dp; 16 17// first... target, second... edge id 18void dfs(int v, int goal){ 19 if(v == goal) return; 20 for(pair<int, int> nv : G[v]){ 21 if(prev_v[nv.first] == -1 || prev_v[nv.first] != INF || nv.first == prev_v[v]) continue; 22 prev_v[nv.first] = v; 23 dfs(nv.first, goal); 24 } 25} 26 27int main(){ 28 ios::sync_with_stdio(false); 29 cin.tie(0); 30 cin >> N >> M >> K; 31 A.resize(M); 32 G.resize(N); 33 for(int i = 0; i < M; ++i){ 34 cin >> A[i]; 35 A[i]--; 36 } 37 for(int i = 0; i < N - 1; ++i){ 38 int a, b; 39 cin >> a >> b; 40 a--; b--; 41 G[a].push_back(make_pair(b, i)); G[b].push_back(make_pair(a, i)); 42 } 43 44 45 C.assign(N - 1, 0); 46 for(int i = 0; i < M - 1; ++i){ 47 prev_v.assign(N, INF); 48 prev_v[A[i]] = -1; 49 dfs(A[i], A[i + 1]); 50 int cur = A[i + 1]; 51 while(cur != A[i]){ 52 int edge_id = G[cur][prev_v[cur]].second; 53 cout << "cur = " << cur << " edge id = " << edge_id << '\n'; 54 C[edge_id]++; 55 cur = prev_v[cur]; 56 57 } 58 } 59 ll sumC = 0; 60 for(int i = 0; i < N - 1; ++i) sumC += C[i]; 61 if((K + sumC) % 2 == 1 || K + sumC < 0){ 62 cout << 0 << endl; 63 return 0; 64 } 65 dp.assign(N, vector<ll>((K + sumC) / 2 + 1 , 0)); 66 dp[0][0] = 1; 67 for(int i = 0; i < N - 1; ++i){ 68 for(int j = 0; j <= (K + sumC) / 2; ++j){ 69 if(C[i] <= j){ 70 dp[i + 1][j] += dp[i][j - C[i]]; 71 dp[i + 1][j] %= mod; 72 } 73 dp[i + 1][j] += dp[i][j]; 74 dp[i + 1][j] %= mod; 75 } 76 } 77 78 cout << dp[N - 1][(K + sumC) / 2] << endl; 79}

試したこと

  • 入力サンプル1を試した結果、自分の環境では2になるが、atcoderのコードテストでは0になる
  • 配列prevには正しい答えが入力されている(以下入力サンプル3を試した場合)

 prev_v = -1 0 1 2 3 4 5 6 7 8
しかしedge_idの値に誤りがあると思われる。実行結果はこのようになったが、
cur = 9 edge id = 3
cur = 8 edge id = 0
cur = 7 edge id = 0
cur = 6 edge id = 8
cur = 5 edge id = 6
cur = 4 edge id = 5
cur = 3 edge id = 3
cur = 2 edge id = 2
cur = 1 edge id = 0
このサンプルケースではどの辺も一回ずつしか通らないはず。これによって、このあとのdpで使う配列Cを正しく作ることができない。

補足情報(FW/ツールのバージョンなど)

使用しているコンパイラ...gcc12.0.5

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

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

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

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

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

actorbug

2021/10/10 08:09

まじめに追ってませんが、vectorの範囲外アクセスがあるようです。 サンプル1を入力すると、以下の箇所(52行目)で範囲外アクセスが見つかります。 int edge_id = G[cur][prev_v[cur]].second;
rdld036

2021/10/12 09:30

グラフGの各要素はpair型で、 37行目の  for(int i = 0; i < N - 1; ++i){ int a, b; cin >> a >> b; a--; b--; G[a].push_back(make_pair(b, i)); G[b].push_back(make_pair(a, i)); } よりsecondにはエッジのidが入っているのではないでしょうか?
actorbug

2021/10/12 20:36 編集

「secondにはエッジのidが入っている」は正しいですが、それが正しかったからと言って、今回の指摘(vectorの範囲外アクセスがある)が誤りであるということにはなりません。 範囲外アクセスがあるのは、C[edge_id]++;ではなく、int edge_id = G[cur][prev_v[cur]].second; になります。 具体的な動作内容を回答に追記したので、そちらで確認をお願いします。 gccを使っているなら、ソースの先頭に「#define _GLIBCXX_DEBUG」を追加してビルド・実行することで、範囲外アクセスを検出することができるそうです。
guest

回答1

0

ベストアンサー

問題は以下の箇所です。

c++

1int edge_id = G[cur][prev_v[cur]].second;

prev_v[cur]は、curの前の頂点のインデックスになります。
そして、G[cur]は、curにつながる辺のvectorになります。
辺のvectorについて、頂点のインデックス番目の要素を取得しているのが誤りです。


具体的に、サンプル1で何が起こっているかを順に追っていきましょう。

まず、入力の読み込みで、A,Gはそれぞれ以下のようになります。
({}はvector、()はpairのつもりです)

A = { 1, 2, 1, 0, 3 };
G = { { (1,0) }, { (0,0), (2,1) }, { (1,1), (3,2) }, { (2,2) } };

読み込んだ後のforループで、i = 1 の場合の処理を追ってみましょう。

まず、

c++

1prev_v.assign(N, INF); 2prev_v[A[i]] = -1; 3dfs(A[i], A[i + 1]);

により、prev_vは以下のようになります。

prev_v = { INF, 2, -1, 2 };

次に、

c++

1int cur = A[i + 1];

により、cur = 1 となります。

その次のwhileの条件は当然満たすとして、問題の以下の処理について考えます。

c++

1int edge_id = G[cur][prev_v[cur]].second;

G[cur] = { (0,0), (2,1) }、prev_v[cur] = 2 なので、
G[cur][prev_v[cur]] は、{ (0,0), (2,1) } の2番目の要素を取得しようとします。
しかし、要素が2つしかないので範囲外アクセスとなります。

投稿2021/10/10 11:02

編集2021/10/12 11:25
actorbug

総合スコア2431

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

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

rdld036

2021/10/15 05:45 編集

返信が遅くなって申し訳ありません。 このように前の頂点に通じる辺を取り出していけば良いということでしょうか。  pair<int, int> edge; for(pair<int, int> e : G[cur]){ if(e.first == prev_v[cur]){ edge = e; } } C[edge.second]++; cur = prev_v[cur];
actorbug

2021/10/15 12:54

そういうことです。
rdld036

2021/10/16 06:58

レビューだけでなく、デバッグの方法についても丁寧にアドバイスしていただき誠にありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問