crignactor
@crignactor (クリグナ)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

競プロでの皆さんの入力の扱い方を教えていただきたいです。

Q&A

Closed

解決したいこと

atcoderのAPG4b、再帰関数の例題について質問があります。(私はプログラミング初心者(1ヶ月)で、C++は独習2日目です。競プロをやる予定の大学生です。)
コード自体は30分もかかったとはいえ短く(簡潔かは不明)書け普通に動作もしたのですが、模範解答を見ると表面的なプロセスだけでなく、発想の仕方の手順が違うように思えました。具体的には、私は入力をそのまま使って目的を果たそうとした一方で、模範解答は入力をあるべき構造に整理してから、それを用いて目的を果たそうとしているという感じです。
そこで質問なのですが、
一般に、競プロなどで解決の手順を考えるとき、皆さんは入力されたデータを整理できるか考えるというプロセスを踏むでしょうか?(木構造などと書いてあったので、そういう有名な(?)構造があるときなどはその構造にデータを落とし込んでから考えるのが定石なのかもと思いました)

例題自体が分からないことはないのく皆さんの入力の扱い方を知りたいのですが、参考までに問題文は載せておきます。

問題文
あなたはA社を経営する社長です。 A社は
N個の組織からなり、それぞれに
0番から
N−1番の番号が付いています。

0番の番号が付いた組織はトップの組織です。

組織間には親子関係があり、

0番以外の

N−1個の組織には必ず1つの親組織があります。 子組織は複数になることがあります。 また、それぞれの組織は直接的または間接的にトップの組織と関係があるものとします。

あなたは全ての組織に報告書を提出するように求めました。
混雑を避けるために、「各組織は子組織の報告書がそろったら、自身の報告書を加えて親組織に送る」ことを繰り返します。 子組織が無いような組織は自身の報告書だけをすぐに親組織に送ります。

ある組織から報告書を送ってから、その親組織が受け取るときにかかる時間を1分とします。
あるタイミングで一斉に報告書の伝達を開始したときに、トップの組織の元に全ての組織の報告書が揃う時刻(伝達を始めてから何分後か)を求めてください。 なお、各組織の報告書は既に準備されているため、報告書の伝達以外の時間はかからないこととします。
AFDA54A6-56A8-46D6-8831-B4CC2333936A.jpeg

私の考えたソースコード

#include <bits/stdc++.h>
using namespace std;

//階層の階数が求める時間よりそれを求める
int steps(int n,vector<int>ps,int N){
vector <int> match(1,0);
for (int i=0;i<N-1;i++){
    if (ps.at(i)== n){
match.push_back(steps(i+1,ps,N)+1);
    }
}
sort(match.begin(),match.end());
int time =match.at(match.size()-1);
return time;
}

int main(){
    int N;
    cin>>N;
  vector<int>ps(N-1);
  for(int i=0 ; i<N-1 ; i++){
    cin>>ps.at(i);
  }
    cout<<steps(0,ps,N)<<endl;
}

模範解答

#include
using namespace std;

// x番の組織について、子組織からの報告書が揃う時刻を返す
// childrenは組織の関係を表す2次元配列(参照渡し)
int complete_time(vector> &children, int x) {
// ベースケース
if (children.at(x).size() == 0) {
return 0; // 子組織が無いような組織について、報告書が揃う時刻は0
}

// 再帰ステップ
int max_receive_time = 0; // 受け取った時刻の最大値
// x番の組織の子組織についてループ
for (int c : children.at(x)) {

// (子組織 c のもとに揃った時刻 + 1) の時刻に c からの報告書を受け取る
int receive_time = complete_time(children, c) + 1;

// 受け取った時刻の最大値 = 揃った時刻 なので最大値を求める
max_receive_time = max(max_receive_time, receive_time);

}
return max_receive_time;
}

// これ以降の行は変更しなくてよい

int main() {
int N;
cin >> N;

vector p(N); // 各組織の親組織を示す配列
p.at(0) = -1; // 0番組織の親組織は存在しないので-1を入れておく
for (int i = 1; i < N; i++) {
cin >> p.at(i);
}

// 組織の関係から2次元配列を作る
vector> children(N); // ある組織の子組織の番号一覧
for (int i = 1; i < N; i++) {
int parent = p.at(i); // i番の親組織の番号
children.at(parent).push_back(i); // parentの子組織一覧にi番を追加
}

// 0番の組織の元に報告書が揃う時刻を求める
cout << complete_time(children, 0) << endl;
}

0

2Answer

AtCoder緑手前の私の意見ですのであまり参考にならないかもしれませんが…

入力をあるべき構造に整理してから、それを用いて目的を果たそうとしている

というのは,常套手段です.大学で競プロ的知識を履修できる科目単位名は大抵,「アルゴリズムとデータ構造」であり,やはりアルゴリズムだけでは成り立たない学問/分野になっています.

したがって,与えられた問題を解けるデータ構造に変換することも多いため,

入力されたデータを整理できるか考えるというプロセスを踏むでしょうか?

の問いにはYesと答えることになります.

データの前処理として他にも

  • ソートしておく
    • argsort
    • TopologicalSort
  • 累積和を求めておく
  • 座標圧縮(座圧)する
  • 種類数を求める
  • stack,queueに入れる
  • 木構造にする
    • 順序付き木(std::setを使う)
    • Heap(std::priority_queueを使う)
    • SegmentTreeを構成する
  • グラフ(向き有りor向き無し)にする
  • 素集合データ構造(UnionFind)を構成する

などなど例をあげればきりがありません.

問題を解く思考の手順として,アルゴリズムを考えるだけでなく,どのデータ構造に落とし込めるか,みたいなアイデアも必要になっていると思っています.

競プロに限った思考手順はある程度限られており,次のサイトが参考になるでしょう.

また,使われるアルゴリズムとデータ構造も,ABCであれば典型問題なことが多いので,データ構造に落とし込むだけで解ける問題もあったりします1

色んなデータ構造のライブラリがまとまったサイトもありますし,

こういった自作ライブラリが正しく動作するかチェックするサイトもあります.

これらライブラリが扱えるように入力データを処理することになるわけです.

コンテストの時間が終わったあとにTwitterで#ABC270のタグとか見たりすると,みんなの解き方がわかるついでに,知らないアルゴリズムとか目にすることもあるのでオススメです.

余談

上で示していただいたコードですが,グラフに関する問題のうち,実質「隣接行列」を使ったときと同じ計算量$O(N^2)$になるような解法となっています.模範解答は「隣接リスト」を使った解法で,$O(N)$ですね,今回は$1\leq N \leq 50$だったのでどちらでも間に合います.

詳しくは次の記事を参考にしてください

  1. ABCのA,B問題では種類数を求めるだけ,だったり,だいぶ前にはD問題でTopologicalソートをするだけ,みたいな問題もありました.

3Like

Comments

  1. 短くコードを書くことをコンセプトとするシーンもあります.「コードゴルフ」という部門ですね.

    ただ,競プロはそうではなく解答速度を競うものになっていますし,AHCでは実行速度とその精度を競っていますね.

    競プロでは最適なアルゴリズムやデータ構造を適用せずにベタ書きした方がコード量が長くなる上に計算量が増えることも多いので,これらを短くできるかどうかでは,アルゴリズムとデータ構造の知識の有無に左右されそうです.

    crignactorさんに越されないよう,私も精進して参ります.「クローズ」にすることお忘れなきよう.

本当に様々な情報をくださり、ありがとうございます。受験では与えられた条件の分解と結合はするものの、私の場合は条件を使って既知のデータを整理するということはしなかったので、慣れるまで時間がかかりそうですが、おかげで頑張っていけそうです。
また余談で言及されている計算量というものを、先程習ったのですが、その活用法という意味でも参考になりました。この概念を習うまでいかにコードを短くするかということくらいしか考えてなかったので目から鱗でした。
ありがとうございました。

0Like

Your answer might help someone💌