SlideShare a Scribd company logo
1 of 59
Download to read offline
図と実装で理解する
木構造入門
@Pro_ktmr
JOI2019 優秀賞
2
注意
• 内容の正確性には十分注意していますが、間
違った情報が含まれることもあります。何か
お気づきのことがあればご連絡ください。
• このスライドのサンプルコードはC++で実装さ
れています。
JOI2019 春合宿
• JOI2019春合宿の問題の解法/概要を見てみる
• Segment Tree
木構造
貪欲法
Segment Tree
Segment Tree
ダイクストラ法
Segment Tree
木構造
動的計画法
Segment Tree
木構造
分割統治
3
JOI2019 春合宿
• JOI2019春合宿の問題の解法/概要を見てみる
• Segment Tree
木構造
貪欲法
Segment Tree
Segment Tree
ダイクストラ法
Segment Tree
木構造
動的計画法
Segment Tree
木構造
分割統治
• 木構造に関する問題が3問も出題されている!
4
JOI春合宿と木構造
• 2019年に限らず、例年2~3問ほど木構造に関
する問題が出題されています
• グラフ理論に見せかけた木構造の問題もあります
• グラフ理論の問題の部分点解法で木を使うこともあります
• Interactive / Communication / Output Onlyでも出題されています
• 木構造に関する問題を解くには典型的な知識が
必要なことも多く、逆に知識があれば簡単なこ
とも多いです
• なので勉強しましょう
5
木構造入門
1. 木構造とは
i. 木構造
ii. 根つき木
iii. 根つき木の実装
2. DFS木
I. 深さ優先探索とDFS木
II. DFS木の作り方
III. DFS木と部分木
3. Euler Tour
I. Euler TourとLCA
6
木構造入門
1. 木構造とは
i. 木構造
ii. 根つき木
iii. 根つき木の実装
2. DFS木
I. 深さ優先探索とDFS木
II. DFS木の作り方
III. DFS木と部分木
3. Euler Tour
I. Euler TourとLCA
7
木構造とは
• 頂点(ノード)と辺(エッジ)か
ら構成されている
• ノードがN個の時、エッジは
N-1本
• 連結(すべてのノード間で行
き来できる)
• 2つのノード間の最短路が一
意に決まる
• 閉路(サイクル)がない
• グラフの特殊な場合で、うれ
しい性質がたくさんあるイ
メージ
8
木構造とは
• 頂点(ノード)と辺(エッジ)か
ら構成されている
• ノードがN個の時、エッジは
N-1本
• 連結(すべてのノード間で行
き来できる)
• 2つのノード間の最短路が一
意に決まる
• 閉路(サイクル)がない
• 誰がどう考えてもこのルート
が明らかに最短
9
終
始
根つき木
• 木のうれしい性質を最大限使
うために、根(ルート)つき木
を考えることが多い
• 問題で根が指定されていない
場合は適当に決めてしまう
10
根つき木
• 木のうれしい性質を最大限使
うために、根(ルート)つき木
を考えることが多い
• 問題で根が指定されていない
場合は適当に決めてしまう
11
根
根つき木
• 木のうれしい性質を最大限使
うために、根(ルート)つき木
を考えることが多い
• 問題で根が指定されていない
場合は適当に決めてしまう
12
根
根つき木
• 木のうれしい性質を最大限使
うために、根(ルート)つき木
を考えることが多い
• 問題で根が指定されていない
場合は適当に決めてしまう
13
根
根つき木
• 木のうれしい性質を最大限使
うために、根(ルート)つき木
を考えることが多い
• 問題で根が指定されていない
場合は適当に決めてしまう
14
根
家族関係
• 各ノードは0個以上の子ノー
ドを持つ
• 根以外のノードは1個の親
ノードを持つ
• 同じ親を持つノードは兄弟
ノードと呼ばれる
• 子ノードやその子ノードなど
は子孫ノードと呼ばれる
• 親ノードやその親ノードなど
は先祖ノードと呼ばれる
• 辺が1本しか出ていない(次数
が1の)ノードを葉と呼ぶ
15
子
自
親
兄
弟
家族関係
• 各ノードは0個以上の子ノー
ドを持つ
• 根以外のノードは1個の親
ノードを持つ
• 同じ親を持つノードは兄弟
ノードと呼ばれる
• 子ノードやその子ノードなど
は子孫ノードと呼ばれる
• 親ノードやその親ノードなど
は先祖ノードと呼ばれる
• 辺が1本しか出ていない(次数
が1の)ノードを葉と呼ぶ
16
3
根つき木の実装
• 配列parent[N]を用意して親
ノードの番号を入れる
• 必要に応じてvector<int>
children[N]を用意して子ノー
ドの一覧も保持しておいても
よい
17
2
5
4
1
0
6
3
根つき木の実装
• 配列parent[N]を用意して親
ノードの番号を入れる
• 必要に応じてvector<int>
children[N]を用意して子ノー
ドの一覧も保持しておいても
よい
• parentって長いので、parと
略す人が多い(気がする)
• 根の親は-1にしたり、自分自
身(par[4] = 4)にする
18
2
5
4
1
0
6
par[4] = -1
par[5] = 4
par[2] = 5
par[1] = 4
par[0] = 4
par[3] = 0 par[6] = 0
3
根つき木の実装
• 配列parent[N]を用意して親
ノードの番号を入れる
• 必要に応じてvector<int>
children[N]を用意して子ノー
ドの一覧も保持しておいても
よい
• chiって略す人は見たことな
い気が・・・
19
2
5
4
1
0
6
chi[4] = {5,1,0}
chi[5] = {2}
chi[2] = {}
chi[1] = {}
chi[0] = {3,6}
chi[3] = {} chi[6] = {}
木構造入門
1. 木構造とは
i. 木構造
ii. 根つき木
iii. 根つき木の実装
2. DFS木
I. 深さ優先探索とDFS木
II. DFS木の作り方
III. DFS木と部分木
3. Euler Tour
I. Euler TourとLCA
20
3
深さ優先探索(DFS)
• 木上で深さ優先探索(DFS)を
行う
• ノードに子があれば次はその
子ノードを探索する
• 子がなくなるまで深く潜って
いく
21
2
5
4
1
0
6
3
DFS木
• DFSの探索順にノード番号を
振りなおすと、DFS木になる
22
2
5
4
1
0
6
3
DFS木
• DFSの探索順にノード番号を
振りなおすと、DFS木になる
23
2
5
0
1
0
6
3
DFS木
• DFSの探索順にノード番号を
振りなおすと、DFS木になる
24
2
1
0
1
0
6
3
DFS木
• DFSの探索順にノード番号を
振りなおすと、DFS木になる
25
2
1
0
1
0
6
3
DFS木
• DFSの探索順にノード番号を
振りなおすと、DFS木になる
26
2
1
0
3
0
6
3
DFS木
• DFSの探索順にノード番号を
振りなおすと、DFS木になる
27
2
1
0
3
4
6
5
DFS木
• DFSの探索順にノード番号を
振りなおすと、DFS木になる
28
2
1
0
3
4
6
5
DFS木
• DFSの探索順にノード番号を
振りなおすと、DFS木になる
29
2
1
0
3
4
6
5
DFS木
• DFSの探索順にノード番号を
振りなおすと、DFS木になる
• 完成!
30
2
1
0
3
4
6
DFS木を作る実装
• 再帰関数でDFSをする
vector<int> children[N]; //もとの子
int par[N],pos[N]; //DFS木における親、DFS終了時のカウンターの状態
int c = 0; //cはカウンターでDFS木のノード番号になる
void dfs(int v){ //vはもとのノード番号
int dfsOrder = c;
c++;
for(int i=0; i<children[v].size(); i++){
par[c] = dfsOrder;
dfs(children[v][i]);
}
pos[dfsOrder] = c;
} //変数が「もと」か「あと(DFS木)」かどちらを表しているのか混乱しないように注意
31
5
DFS木の性質「部分木」
• あるノードvについて、その
子孫のDFSが終了したタイミ
ングでのカウンターをpos[v]
として保存しておく
• vを根とする部分木は
[v,pos[v])である
• [a,b)は半開区間でa≦i<bみ
たいな
32
2
1
0
3
4
6
pos[0] = 7
pos[1] = 3
pos[5] = 6
pos[4] = 7
pos[3] = 4
pos[2] = 3
pos[6] = 7
5
DFS木の性質「部分木」
• あるノードvについて、その
子孫のDFSが終了したタイミ
ングでのカウンターをpos[v]
として保存しておく
• vを根とする部分木は
[v,pos[v])である
• [a,b)は半開区間でa≦i<bみ
たいな
33
2
1
0
3
4
6
pos[0] = 7
pos[1] = 3
pos[5] = 6
pos[4] = 7
pos[3] = 4
pos[2] = 3
pos[6] = 7
ノード4を根とする部分木は
ノード[4,7) つまり4,5,6からなる
5
部分木の使い方
• RAQやRUQを使って部分木に
対して一気に操作することが
できる
• 例)各辺にコストが設定され
ている。以下のクエリを処理
する。
①頂点vとその親との間の辺
のコストをxに変更する
②頂点vと根との最短距離dを
求める
34
2
1
0
3
4
6
v 0 1 2 3 4 5 6
d 0 5 6 2 4 4 5
5
1
2
4
0 1
5
部分木の使い方
• RAQやRUQを使って部分木に
対して一気に操作することが
できる
• 例)各辺にコストが設定され
ている。以下のクエリを処理
する。
①頂点vとその親との間の辺
のコストをxに変更する
②頂点vと根との最短距離dを
求める
• クエリ:①v=4,x=2
35
2
1
0
3
4
6
v 0 1 2 3 4 5 6
d 0 5 6 2 2 2 3
5
1
2
2
0 1
コスト:4→2なので
部分木全部RAQ:-2
5
部分木の使い方
• RAQやRUQを使って部分木に
対して一気に操作することが
できる
• 例)各辺にコストが設定され
ている。以下のクエリを処理
する。
①頂点vとその親との間の辺
のコストをxに変更する
②頂点vと根との最短距離dを
求める
• クエリ:②v=5
36
2
1
0
3
4
6
v 0 1 2 3 4 5 6
d 0 5 6 2 2 2 3
5
1
2
2
0 1
補足:RAQとRUQとRMQ
• RAQ:Range Add Query
配列の特定の区間に対して値を加算する
• RUQ:Range Update Query
配列の特定の区間に対して値を変更する
• どちらも遅延評価Segment Treeや平方分割で
実装できます
• RMQ:Range Minimum Query
配列の特定の区間の値の最小値を求める
も後で出てきます
37
木構造入門
1. 木構造とは
i. 木構造
ii. 根つき木
iii. 根つき木の実装
2. DFS木
I. 深さ優先探索とDFS木
II. DFS木の作り方
III. DFS木と部分木
3. Euler Tour
I. Euler TourとLCA
38
5
Euler Tour
• 頂点のDFSでの訪問順を
vector等で持つことで、木を
直線にできる
39
2
1
0
3
4
6
5
Euler Tour
• 頂点のDFSでの訪問順を
vector等で持つことで、木を
直線にできる
• 0 → 1 → 2 → 1 → 0 → 3 → 0
→ 4 → 5 → 4 → 6 → 4 → 0
40
2
1
0
3
4
6
5
Euler Tour
• 頂点のDFSでの訪問順を
vector等で持つことで、木を
直線にできる
• 0,1,2,1,0,3,0,4,5,4,6,4,0
• この配列の要素数は頂点数を
Nとして2N-1個になる
41
2
1
0
3
4
6
Euler Tourの実装
vector<int> EulerTour;
int pre[N];
void dfs(int v){
pre[v] = EulerTour.size();
EulerTour.push_back(v);
for(int i=0; i<children[v].size(); i++){
dfs(edge[v][i]);
EulerTour.push_back(v);
}
}
• 関数名がdfsでさっきと被ってるが、その辺りは臨機応変に対
応する
42
2
深さ
• ところで、あるノードの根から
の距離を深さと呼ぶ
• 再帰関数で求められる
int depth(int v){
if(par[v] == -1) return 0;
return depth(par[v])+1;
}
• 木の構造が変わらないならメモ
化するとよい
int d[N] = {};
int depth(int v){
if(par[v] == -1) return 0;
if(d[v] != 0) return d[v];
return d[v] = depth(par[v])+1;
}
43
2
1
0
1
1
2
LCA(最近共通祖先)
• Lowest Common Ancestor
• 2つ以上のノードについて共
通な祖先のうちもっとも深い
もの
44
LCA(最近共通祖先)
• Lowest Common Ancestor
• 2つ以上のノードについて共
通な祖先のうちもっとも深い
もの
45
0
1
LCA(0,1)
2
LCA(最近共通祖先)
• Lowest Common Ancestor
• 2つ以上のノードについて共
通な祖先のうちもっとも深い
もの
46
1
LCA(2,1)
5
LCAとRMQ
• さっきのスライドより右の木
のEuler Tourは
0,1,2,1,0,3,0,4,5,4,6,4,0
• 頂点vを最初に訪問した時の
pushする前のvectorの要素
数をpre[v]とする
47
2
1
0
3
4
6
5
LCAとRMQ
• さっきのスライドより右の木
のEuler Tourは
0,1,2,1,0,3,0,4,5,4,6,4,0
• 頂点vを最初に訪問した時の
pushする前のvectorの要素
数をpre[v]とする
• 2つの頂点i, j (pre[i] < pre[j])
のLCAは
RMQ[pre[i],pre[j])になる
48
2
1
0
3
4
6
pre[0] = 0
pre[1] = 1
pre[2] = 2
pre[3] = 5
pre[4] = 7
pre[5] = 8
pre[6] = 10
LCAとRMQ
•
0,1,2,1,0,3,0,4,5,4,6,4,0
49
pre[0] = 0
pre[1] = 1
pre[2] = 2
pre[3] = 5
pre[4] = 7
pre[5] = 8
pre[6] = 10
LCAとRMQ
50
pre[0] = 0
pre[1] = 1
pre[2] = 2
pre[3] = 5
pre[4] = 7
pre[5] = 8
pre[6] = 10
Euler Tour 0 1 2 3 4 5 6 7 8 9 10 11 12
v 0 1 2 1 0 3 0 4 5 4 6 4 0
LCAとRMQ
51
Euler Tour 0 1 2 3 4 5 6 7 8 9 10 11 12
v 0 1 2 1 0 3 0 4 5 4 6 4 0
v 0 1 2 3 4 5 6
pre[v] 0 1 2 5 7 8 10
LCAとRMQ
• 頂点2と5のLCAは?
52
Euler Tour 0 1 2 3 4 5 6 7 8 9 10 11 12
v 0 1 2 1 0 3 0 4 5 4 6 4 0
v 0 1 2 3 4 5 6
pre[v] 0 1 2 5 7 8 10
LCAとRMQ
• 頂点2と5のLCAは?
Euler TourにおけるRMQ[pre[2]=2,pre[5]=8)
→0
• 数スライド戻ると確かにLCA(2,5)=0
53
Euler Tour 0 1 2 3 4 5 6 7 8 9 10 11 12
v 0 1 2 1 0 3 0 4 5 4 6 4 0
v 0 1 2 3 4 5 6
pre[v] 0 1 2 5 7 8 10
LCAとRMQ
• 木構造を完全に忘れてただのRMQとしてLCAを
求めることができる
• 他にはDoublingやHL分解などでもLCAを求め
られる
• RMQを使うにはSegment Tree(or平方分割)を
実装する必要があるが、個人的にはEuler
Tour→RMQが好き
54
まとめ
「木があるということは数列があるということ」
55
まとめ
• 木構造の問題に対しては、適当な頂点を根とし
てDFS木を作ると良いことが多い
• 木があるということは数列があるということ
vを根とした部分木は[v,pos[v])
• LCA(i,j)はEuler Tourにおける
RMQ[pre[i],pre[j])で求められる
• 他にも辺や頂点をなくす(くっつける)場合の
UnionFindによる縮約もJOI春合宿では頻出
56
練習問題
• AOJ GRL_5_C LCA
https://onlinejudge.u-
aizu.ac.jp/courses/library/5/GRL/5/GRL_5_C
• AOJ GRL_5_D Range Query on a Tree
https://onlinejudge.u-
aizu.ac.jp/courses/library/5/GRL/5/GRL_5_D
• 最初はAOJで基礎を固めるのがおすすめです
57
練習問題
• JOI春合宿2015 Day2-3 Road Development
https://atcoder.jp/contests/joisc2015/tasks/j
oisc2015_g
• このスライドの内容をすべて網羅しているよう
な問題で、難しく重実装ですが、これさえ解け
ればかなり力が付きます
58
図と実装で理解する『木構造入門』

More Related Content

What's hot

プログラミングコンテストでの乱択アルゴリズム
プログラミングコンテストでの乱択アルゴリズムプログラミングコンテストでの乱択アルゴリズム
プログラミングコンテストでの乱択アルゴリズムTakuya Akiba
 
Rolling Hashを殺す話
Rolling Hashを殺す話Rolling Hashを殺す話
Rolling Hashを殺す話Nagisa Eto
 
勉強か?趣味か?人生か?―プログラミングコンテストとは
勉強か?趣味か?人生か?―プログラミングコンテストとは勉強か?趣味か?人生か?―プログラミングコンテストとは
勉強か?趣味か?人生か?―プログラミングコンテストとはTakuya Akiba
 
ユークリッド最小全域木
ユークリッド最小全域木ユークリッド最小全域木
ユークリッド最小全域木理玖 川崎
 
色々なダイクストラ高速化
色々なダイクストラ高速化色々なダイクストラ高速化
色々なダイクストラ高速化yosupo
 
プログラミングコンテストでの動的計画法
プログラミングコンテストでの動的計画法プログラミングコンテストでの動的計画法
プログラミングコンテストでの動的計画法Takuya Akiba
 
二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方
二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方
二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方Kensuke Otsuki
 
2SAT(充足可能性問題)の解き方
2SAT(充足可能性問題)の解き方2SAT(充足可能性問題)の解き方
2SAT(充足可能性問題)の解き方Tsuneo Yoshioka
 
ウェーブレット木の世界
ウェーブレット木の世界ウェーブレット木の世界
ウェーブレット木の世界Preferred Networks
 
様々な全域木問題
様々な全域木問題様々な全域木問題
様々な全域木問題tmaehara
 
AtCoder Beginner Contest 035 解説
AtCoder Beginner Contest 035 解説AtCoder Beginner Contest 035 解説
AtCoder Beginner Contest 035 解説AtCoder Inc.
 
Union find(素集合データ構造)
Union find(素集合データ構造)Union find(素集合データ構造)
Union find(素集合データ構造)AtCoder Inc.
 

What's hot (20)

プログラミングコンテストでの乱択アルゴリズム
プログラミングコンテストでの乱択アルゴリズムプログラミングコンテストでの乱択アルゴリズム
プログラミングコンテストでの乱択アルゴリズム
 
Rolling Hashを殺す話
Rolling Hashを殺す話Rolling Hashを殺す話
Rolling Hashを殺す話
 
勉強か?趣味か?人生か?―プログラミングコンテストとは
勉強か?趣味か?人生か?―プログラミングコンテストとは勉強か?趣味か?人生か?―プログラミングコンテストとは
勉強か?趣味か?人生か?―プログラミングコンテストとは
 
Convex Hull Trick
Convex Hull TrickConvex Hull Trick
Convex Hull Trick
 
ユークリッド最小全域木
ユークリッド最小全域木ユークリッド最小全域木
ユークリッド最小全域木
 
Rolling hash
Rolling hashRolling hash
Rolling hash
 
Binary indexed tree
Binary indexed treeBinary indexed tree
Binary indexed tree
 
色々なダイクストラ高速化
色々なダイクストラ高速化色々なダイクストラ高速化
色々なダイクストラ高速化
 
双対性
双対性双対性
双対性
 
プログラミングコンテストでの動的計画法
プログラミングコンテストでの動的計画法プログラミングコンテストでの動的計画法
プログラミングコンテストでの動的計画法
 
RMQ クエリ処理
RMQ クエリ処理RMQ クエリ処理
RMQ クエリ処理
 
二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方
二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方
二部グラフの最小点被覆と最大安定集合と最小辺被覆の求め方
 
直交領域探索
直交領域探索直交領域探索
直交領域探索
 
動的計画法を極める!
動的計画法を極める!動的計画法を極める!
動的計画法を極める!
 
2SAT(充足可能性問題)の解き方
2SAT(充足可能性問題)の解き方2SAT(充足可能性問題)の解き方
2SAT(充足可能性問題)の解き方
 
Topological sort
Topological sortTopological sort
Topological sort
 
ウェーブレット木の世界
ウェーブレット木の世界ウェーブレット木の世界
ウェーブレット木の世界
 
様々な全域木問題
様々な全域木問題様々な全域木問題
様々な全域木問題
 
AtCoder Beginner Contest 035 解説
AtCoder Beginner Contest 035 解説AtCoder Beginner Contest 035 解説
AtCoder Beginner Contest 035 解説
 
Union find(素集合データ構造)
Union find(素集合データ構造)Union find(素集合データ構造)
Union find(素集合データ構造)
 

Similar to 図と実装で理解する『木構造入門』

競プロは人生の役に立つ!
競プロは人生の役に立つ!競プロは人生の役に立つ!
競プロは人生の役に立つ!Kensuke Otsuki
 
区間分割の仕方を最適化する動的計画法 (JOI 2021 夏季セミナー)
区間分割の仕方を最適化する動的計画法 (JOI 2021 夏季セミナー)区間分割の仕方を最適化する動的計画法 (JOI 2021 夏季セミナー)
区間分割の仕方を最適化する動的計画法 (JOI 2021 夏季セミナー)Kensuke Otsuki
 
Operations research yonezawa_no1
Operations research yonezawa_no1Operations research yonezawa_no1
Operations research yonezawa_no1ssuser0bebd2
 
T82 aoitan あおいたんのパズルを数学しましょうか_修正版
T82 aoitan あおいたんのパズルを数学しましょうか_修正版T82 aoitan あおいたんのパズルを数学しましょうか_修正版
T82 aoitan あおいたんのパズルを数学しましょうか_修正版Masami Yabushita
 
わんくま勉強会東京#82 あおいたんのパズルを数学しましょうか
わんくま勉強会東京#82 あおいたんのパズルを数学しましょうかわんくま勉強会東京#82 あおいたんのパズルを数学しましょうか
わんくま勉強会東京#82 あおいたんのパズルを数学しましょうかMasami Yabushita
 
Rubysapporo Stringsearch
Rubysapporo StringsearchRubysapporo Stringsearch
Rubysapporo StringsearchAkio Ishida
 
AtCoder Beginner Contest 006 解説
AtCoder Beginner Contest 006 解説AtCoder Beginner Contest 006 解説
AtCoder Beginner Contest 006 解説AtCoder Inc.
 

Similar to 図と実装で理解する『木構造入門』 (9)

競プロは人生の役に立つ!
競プロは人生の役に立つ!競プロは人生の役に立つ!
競プロは人生の役に立つ!
 
区間分割の仕方を最適化する動的計画法 (JOI 2021 夏季セミナー)
区間分割の仕方を最適化する動的計画法 (JOI 2021 夏季セミナー)区間分割の仕方を最適化する動的計画法 (JOI 2021 夏季セミナー)
区間分割の仕方を最適化する動的計画法 (JOI 2021 夏季セミナー)
 
Aizu-2017: B
Aizu-2017: BAizu-2017: B
Aizu-2017: B
 
Operations research yonezawa_no1
Operations research yonezawa_no1Operations research yonezawa_no1
Operations research yonezawa_no1
 
T82 aoitan あおいたんのパズルを数学しましょうか_修正版
T82 aoitan あおいたんのパズルを数学しましょうか_修正版T82 aoitan あおいたんのパズルを数学しましょうか_修正版
T82 aoitan あおいたんのパズルを数学しましょうか_修正版
 
わんくま勉強会東京#82 あおいたんのパズルを数学しましょうか
わんくま勉強会東京#82 あおいたんのパズルを数学しましょうかわんくま勉強会東京#82 あおいたんのパズルを数学しましょうか
わんくま勉強会東京#82 あおいたんのパズルを数学しましょうか
 
Rubysapporo Stringsearch
Rubysapporo StringsearchRubysapporo Stringsearch
Rubysapporo Stringsearch
 
AtCoder Beginner Contest 006 解説
AtCoder Beginner Contest 006 解説AtCoder Beginner Contest 006 解説
AtCoder Beginner Contest 006 解説
 
210122 msi dp
210122 msi dp210122 msi dp
210122 msi dp
 

図と実装で理解する『木構造入門』