SlideShare ist ein Scribd-Unternehmen logo
1 von 25
Downloaden Sie, um offline zu lesen
ARC030 解説 
解説スライド担当: 三上 和馬 
(@kyuridenamida)
問題A – 閉路グラフ
問題概要 
• 
n = 6 k = 2 の例 答えは「YES」
解法&考察 
• 取り出せる最大の連結成分の数を求めたい 
• とりあえず頂点を1つ取り除いてみると直線グラフになる 
1 2 3 4 5 
● この状態でサイズ1の連結成分を作りまくるのが最適そう 
したがって端から偶数番目の頂点を取り除いていけばよい 
●奇数番目の頂点の数=((n-1)/2を切り上げたもの)が作れる連結成分の最 
大数であり,kがこれ以下であればYES,そうでなければNOを出力 
•
備考 
• 閉路グラフはよく列を扱う問題の派生として出題されます. 
• 「頂点を1つ○○したら列の問題に帰着できる」等の発想は,方針を 
考える上で手助けになるかと思われます.
問題B – ツリーグラフ
問題概要 
• 
x=1のケース
考察&解法 
●与えられたグラフから出発地点を根とした根付き木を構築する 
● ある辺をたどるべきかどうかだが,「辺を辿った先の部分木の頂点が 
1つでも宝石を含んでいるときに限り通り,それ以外のとき通らなくて 
良い」ということが分かる.部分木が宝石を含んでいるかを判定する 
関数を作っておく 
● ある辺を使うときは行きと帰りで必ず2回通る 
● なので,判定関数で,行く必要のない頂点に遷移しないような再帰関 
数を書き,辺をたどる毎に答えに2を加算するプログラムを書けば良 
い 
●任意のケースで同じ辺を3回以上辿る必要はなく,上記の戦略が最 
適.
問題C – 有向グラフ
問題概要 
1 
a 
2 
b 
4 
a 
3 
b 
①a回収 
②移動 
④a回収 
⑤移動 
③移動 
⑥b回収 
頂点4からスタート 
k=3のケース aabを出力
強連結成分分解 
●有向グラフにおいて,相互に行き来できる頂点同士の集合 
(強連結成分)を検出し,グループ分けするアルゴリズム 
●適当な未訪問の頂点を始点とし,深さ優先探索を行い,帰り 
がけ順で頂点を列挙することを,未訪問頂点がなくなるまで 
繰り返す(トポロジカルソート). 
●次に,元のグラフの逆辺グラフを考え,上記で得られた頂点 
の順番に,未訪問の頂点を始点とし再度深さ優先探索を繰 
り返し行う.このとき,この一度の深さ優先探索でたどり着け 
る頂点集合が1つの強連結成分となっている
考察&解法 
• どこの頂点のアルファベットを回収したかを覚えて探索は無理. 
• なので,与えられたグラフに対して強連結成分分解を行う 
• 同じ強連結成分に属する頂点の文字は,自由な順番で取ることがで 
きる→ある強連結成分にある文字をhoge個使うことを考えたとき,強 
連結成分に含まれる文字群をソートした文字列の先頭hoge個を使え 
ば良いということが分かる 
• こういった遷移を考えればDAGになり,回収した頂点を覚えなくて良 
いDPに帰着できる 
• DPは「どの強連結成分か」「今何文字か」をキーに辞書順最小の文 
字列を格納するものを行う 
• 強連結成分分解の計算量はO(N+M),文字列比較に最悪O(K)かか 
るので,動的計画法の計算量はO((NK+M)K)
問題D – グラフではない
問題概要 
• 長さNの数列Xが与えられ,その後Q個のクエリが飛んでくる.種類は 
• 1 a b v ― Xの区間 [a,b] に一様に値 v を加える. 
• 2 a b c d ― Xの区間 [a,b] を,クエリが呼ばれた時点での区間 [c,d] の値に書き換える(b−a=d−c). 
• 3 a b ― Xの区間 [a,b] の総和を出力. 
• 1 ≦ N ≦ 200,000 
• 1 ≦ Q ≦ 200,000
方針 
今回のクエリはタイプ2が曲者なので,そのため 
区間に対する操作は平衡二分木等を使うと万能なのでそれを使う.遅 
延評価が使えると良い. 
  segtreeは使えるか…? → 今回はタイプ2があり,使いづらい. 
● タイプ2のクエリ([c,d]→[a,b])がやっかい 
このクエリは,区間[c,d]のコピーを区間[a,b]に上書きするクエリ 
平衡二分木のある区間のコピーを作成するには,永続データ構造が 
必要そう. 
遅延評価機能付き永続型平衡二分木をつくろう.
永続化のための平衡二分木選び 
• ✖ treap (同じ乱数値を持つノードがたくさん発生するため木が偏る) 
• ○ 赤黒木 (いける...けど実装重い) 
• ◎ RandomizedBinarySearchTree (マージ/スプリットするときに乱数を 
用いるのでtreapみたいなことは起きない) 
 ↑計算量解析は難しいらしいが実際偏らず各操作O(log n).実装も 
比較的軽くおすすめ!今回はこれを実装する.
まずは普通の平衡二分木 
• 今回はキーに順序関係が全くないので,「平衡二分探索木」ではなく 
「平衡二分木」と呼ぶが,対数計算量実現のためにやってる操作は 
一緒. 
• 区間の問題に対処するためには, 
– ある位置の頂点を追加する/削除するというinsert/eraseベース 
の実装 
• よりも 
– ある位置で木を2つに分割する/2つの木を順番を保って併合す 
るsplit/mergeベースの実装 
のほうが都合が良いのでそれを実装していく
各ノードが持つべき情報 
●左の子へのポインタ/右の子へのポインタ 
● そのノードの持つ値 
● そのノードを根とした部分木のすべてのノードの値の総和 
● そのノードを根とした部分木のすべてのノードの数 
● そのノードが持つ評価遅延中の値 
が持つべき情報 
struct T{ 
T *l,*r; 
long long v; 
long long sum; 
long long lazy; 
int c; 
};
作っておくと便利な関数 
●子の情報に基づきノードの情報を更新し,自身のポインタを返す関数 
T *update(T *c){ 
if( c == NULL ) return c; 
if( c->lazy ){ // 評価遅延しているものを評価し子に伝搬する 
c->v += c->lazy; 
if(c->l){ 
c->l->lazy += c->lazy; 
c->l->sum += count(c->l) * c->lazy; 
} 
if(c->r){ 
c->r->lazy += c->lazy; 
c->r->sum += count(c->r) * c->lazy; 
} 
c->lazy = 0; 
} 
c->c = count(c->l) + count(c->r) + 1; 
c->sum = sum(c->l) + sum(c->r) + c->v; 
return c; 
} 
int count(T *c){ 
if( !c ) return 0; 
else return c->c; 
} 
long long sum(T *c){ 
if( !c ) return 0; 
else return c->sum; 
}
マージ関数の実装 
● T *merge(T *a,T *b) := aの右の子にbを結合するか,bの左の子にa 
を結合するかを乱数で決める関数 
「aの部分木サイズ>bの部分木のサイズ」のときはだいたいの確率 
でaの右の子にbをくっつける」などを繰り返すイメージ 
● つなげたあとは,つなげたときに取り除いた子を考慮して再帰 
T *merge(T *a,T *b){ 
if(a == NULL) return b; 
if(b == NULL ) return a; 
if( rand() % ( count(a) + count(b) ) < count(a) ){ 
a = update(a); // たどったノードはとりあえず更新しておく 
a->r = merge(a->r,b); 
return update(a); 
}else{ 
b = update(b); // たどったノードはとりあえず更新しておく 
b->l = merge(a,b->l); 
return update(b); 
} 
}
スプリット関数の実装 
● pair<T*,T*> split(T *c,int k) := cの部分木を[0,k) [k,n)に分割したもの 
のそれぞれの根をpairで返す 
pair<T*,T*> split(T *c,int k){ 
if(!c) return make_pair(c,c); 
c = update(c); //たどったノードはとりあえず更新しておく 
if(k <= count(c->l)){ 
pair<T*,T*> s = split(c->l,k); 
c->l = s.second; 
return make_pair(s.first,update(c)); 
}else{ 
pair<T*,T*> s = split(c->r,k - count(c->l) - 1); 
c->r = s.first; 
return make_pair(update(c),s.second); 
} 
}
これらの関数を永続化に対応させるには 
● 基本的に全てのノードがconstなものであると考え,子を書き換えた 
り,遅延情報を伝搬したり,ありとあらゆる変更する際はコピーノー 
ドを作成し,それを書き換えるという発想に基づく.書き換わってい 
ないノードは既存のものを使うようにする. 
● ここで自身を返す実装が初めて生きてくる 
● すると,一度の操作で増えるノードの数はたかだかO(log N)個
これらの関数をこの問題に適用していく 
● 基本的に,区間に対する操作は, 
– 区間だけを切り取った木をsplitで作り,根を操作し,そのあとそれをmergeする 
で対処する. 
● たとえば区間[a,b)に対するタイプ1(一様加算)クエリは今の列の[a, 
b)を取り出して根の遅延評価情報にvを加算し,くっつける 
● タイプ2(コピー)クエリに関しては,[0,a) [b,n) そして[c,d) をコピーし 
た木を作り,[0,a) [c,d) [b,n)の順番でマージする(実際の問題は閉 
区間で与えられるので注意) 
● タイプ3のクエリは,同様に区間を切り出し根のsumを出力するだけ
メモリが溢れることに対する対処 
● 実際は,一回クエリを処理するたびに,ノードが結構生成される 
● 適当にやっているとMLEしかねないが,メモリーがやばくなったら一 
回列の情報を全てどこかに出力し,永続情報を破棄し再構築する 
という方法で簡単にごまかすことができる.
計算量 
● 時間計算量 
– 一回の操作でO(log N) なので O( (Q+N) log N) 
– ただし定数倍は重い 
● 空間計算量 
– O( (N+Q) log N)個のノードがクエリ処理の過程で生成される 
– 遅延評価するときに,たどったノードに隣接している子ノードの分もコピーするので,こちら 
も定数倍は重い.前スライドのような方法で対処.

Weitere ähnliche Inhalte

Was ist angesagt?

Was ist angesagt? (20)

AtCoder Beginner Contest 022 解説
AtCoder Beginner Contest 022 解説AtCoder Beginner Contest 022 解説
AtCoder Beginner Contest 022 解説
 
Disco Presents ディスカバリーチャンネルプログラミングコンテスト2016 本選 解説
Disco Presents ディスカバリーチャンネルプログラミングコンテスト2016 本選 解説Disco Presents ディスカバリーチャンネルプログラミングコンテスト2016 本選 解説
Disco Presents ディスカバリーチャンネルプログラミングコンテスト2016 本選 解説
 
AtCoder Regular Contest 032 解説
AtCoder Regular Contest 032 解説AtCoder Regular Contest 032 解説
AtCoder Regular Contest 032 解説
 
arc047
arc047arc047
arc047
 
abc032
abc032abc032
abc032
 
AtCoder Regular Contest 036 解説
AtCoder Regular Contest 036 解説AtCoder Regular Contest 036 解説
AtCoder Regular Contest 036 解説
 
AtCoder Beginner Contest 011 解説
AtCoder Beginner Contest 011 解説AtCoder Beginner Contest 011 解説
AtCoder Beginner Contest 011 解説
 
AtCoder Regular Contest 017
AtCoder Regular Contest 017AtCoder Regular Contest 017
AtCoder Regular Contest 017
 
AtCoder Regular Contest 021 解説
AtCoder Regular Contest 021 解説AtCoder Regular Contest 021 解説
AtCoder Regular Contest 021 解説
 
AtCoder Regular Contest 042 解説
AtCoder Regular Contest 042 解説AtCoder Regular Contest 042 解説
AtCoder Regular Contest 042 解説
 
AtCoder Beginner Contest 020 解説
AtCoder Beginner Contest 020 解説AtCoder Beginner Contest 020 解説
AtCoder Beginner Contest 020 解説
 
AtCoder Regular Contest 035 解説
AtCoder Regular Contest 035 解説AtCoder Regular Contest 035 解説
AtCoder Regular Contest 035 解説
 
abc027
abc027abc027
abc027
 
AtCoder Beginner Contest 013 解説
AtCoder Beginner Contest 013 解説AtCoder Beginner Contest 013 解説
AtCoder Beginner Contest 013 解説
 
AtCoder Regular Contest 027 解説
AtCoder Regular Contest 027 解説AtCoder Regular Contest 027 解説
AtCoder Regular Contest 027 解説
 
Arc015途中まで解説
Arc015途中まで解説Arc015途中まで解説
Arc015途中まで解説
 
AtCoder Beginner Contest 025 解説
AtCoder Beginner Contest 025 解説AtCoder Beginner Contest 025 解説
AtCoder Beginner Contest 025 解説
 
AtCoder Beginner Contest 029 解説
AtCoder Beginner Contest 029 解説AtCoder Beginner Contest 029 解説
AtCoder Beginner Contest 029 解説
 
AtCoder Beginner Contest 016 解説
AtCoder Beginner Contest 016 解説AtCoder Beginner Contest 016 解説
AtCoder Beginner Contest 016 解説
 
AtCoder Beginner Contest 010 解説
AtCoder Beginner Contest 010 解説AtCoder Beginner Contest 010 解説
AtCoder Beginner Contest 010 解説
 

Ähnlich wie AtCoder Regular Contest 030 解説

Trip
TripTrip
Trip
oupc
 
競技プログラミングでの線型方程式系
競技プログラミングでの線型方程式系競技プログラミングでの線型方程式系
競技プログラミングでの線型方程式系
tmaehara
 
組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由
kikairoya
 
Fiekppteste 130709205838-phpapp02
Fiekppteste 130709205838-phpapp02Fiekppteste 130709205838-phpapp02
Fiekppteste 130709205838-phpapp02
Arbenng
 

Ähnlich wie AtCoder Regular Contest 030 解説 (20)

Sec15 dynamic programming
Sec15 dynamic programmingSec15 dynamic programming
Sec15 dynamic programming
 
Donutsプロコンチャレンジ 2015 解説
Donutsプロコンチャレンジ 2015 解説Donutsプロコンチャレンジ 2015 解説
Donutsプロコンチャレンジ 2015 解説
 
Algebraic DP: 動的計画法を書きやすく
Algebraic DP: 動的計画法を書きやすくAlgebraic DP: 動的計画法を書きやすく
Algebraic DP: 動的計画法を書きやすく
 
代数的実数とCADの実装紹介
代数的実数とCADの実装紹介代数的実数とCADの実装紹介
代数的実数とCADの実装紹介
 
20190625 OpenACC 講習会 第3部
20190625 OpenACC 講習会 第3部20190625 OpenACC 講習会 第3部
20190625 OpenACC 講習会 第3部
 
Trip
TripTrip
Trip
 
JOIss2020 発表資料
JOIss2020 発表資料JOIss2020 発表資料
JOIss2020 発表資料
 
Convex Hull Trick
Convex Hull TrickConvex Hull Trick
Convex Hull Trick
 
Kosakunakano
KosakunakanoKosakunakano
Kosakunakano
 
第9回スキル養成講座講義資料
第9回スキル養成講座講義資料第9回スキル養成講座講義資料
第9回スキル養成講座講義資料
 
競技プログラミングでの線型方程式系
競技プログラミングでの線型方程式系競技プログラミングでの線型方程式系
競技プログラミングでの線型方程式系
 
凸最適化 〜 双対定理とソルバーCVXPYの紹介 〜
凸最適化 〜 双対定理とソルバーCVXPYの紹介 〜凸最適化 〜 双対定理とソルバーCVXPYの紹介 〜
凸最適化 〜 双対定理とソルバーCVXPYの紹介 〜
 
何もないところから数を作る
何もないところから数を作る何もないところから数を作る
何もないところから数を作る
 
組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由
 
PostgreSQL SQLチューニング入門 実践編(pgcon14j)
PostgreSQL SQLチューニング入門 実践編(pgcon14j)PostgreSQL SQLチューニング入門 実践編(pgcon14j)
PostgreSQL SQLチューニング入門 実践編(pgcon14j)
 
Fiekppteste 130709205838-phpapp02
Fiekppteste 130709205838-phpapp02Fiekppteste 130709205838-phpapp02
Fiekppteste 130709205838-phpapp02
 
FIEK provime pranuese teste.
FIEK provime pranuese teste.FIEK provime pranuese teste.
FIEK provime pranuese teste.
 
人工無脳バトル 1st STEP 回答と解説
人工無脳バトル 1st STEP 回答と解説人工無脳バトル 1st STEP 回答と解説
人工無脳バトル 1st STEP 回答と解説
 
RとStanでクラウドセットアップ時間を分析してみたら #TokyoR
RとStanでクラウドセットアップ時間を分析してみたら #TokyoRRとStanでクラウドセットアップ時間を分析してみたら #TokyoR
RとStanでクラウドセットアップ時間を分析してみたら #TokyoR
 
【Unity道場】ゲーム制作に使う数学を学習しよう
【Unity道場】ゲーム制作に使う数学を学習しよう【Unity道場】ゲーム制作に使う数学を学習しよう
【Unity道場】ゲーム制作に使う数学を学習しよう
 

Mehr von AtCoder Inc.

Mehr von AtCoder Inc. (20)

TCO2017R1
TCO2017R1TCO2017R1
TCO2017R1
 
AtCoderに毎回参加したくなる仕組み
AtCoderに毎回参加したくなる仕組みAtCoderに毎回参加したくなる仕組み
AtCoderに毎回参加したくなる仕組み
 
Square869120 contest #2
Square869120 contest #2Square869120 contest #2
Square869120 contest #2
 
AtCoder Beginner Contest 035 解説
AtCoder Beginner Contest 035 解説AtCoder Beginner Contest 035 解説
AtCoder Beginner Contest 035 解説
 
Chokudai Contest 001
Chokudai Contest 001Chokudai Contest 001
Chokudai Contest 001
 
AtCoder Regular Contest 049 解説
AtCoder Regular Contest 049 解説AtCoder Regular Contest 049 解説
AtCoder Regular Contest 049 解説
 
AtCoder Beginner Contest 034 解説
AtCoder Beginner Contest 034 解説AtCoder Beginner Contest 034 解説
AtCoder Beginner Contest 034 解説
 
AtCoder Regular Contest 048
AtCoder Regular Contest 048AtCoder Regular Contest 048
AtCoder Regular Contest 048
 
MUJINプログラミングチャレンジ2016 解説
MUJINプログラミングチャレンジ2016 解説MUJINプログラミングチャレンジ2016 解説
MUJINプログラミングチャレンジ2016 解説
 
AtCoder Beginner Contest 033 解説
AtCoder Beginner Contest 033 解説AtCoder Beginner Contest 033 解説
AtCoder Beginner Contest 033 解説
 
DDPC 2016 予選 解説
DDPC 2016 予選 解説DDPC 2016 予選 解説
DDPC 2016 予選 解説
 
CODE FESTIVAL 2015 沖縄ツアー 解説
CODE FESTIVAL 2015 沖縄ツアー 解説CODE FESTIVAL 2015 沖縄ツアー 解説
CODE FESTIVAL 2015 沖縄ツアー 解説
 
AtCoder Regular Contest 046
AtCoder Regular Contest 046AtCoder Regular Contest 046
AtCoder Regular Contest 046
 
abc031
abc031abc031
abc031
 
CODE FESTIVAL 2015 解説
CODE FESTIVAL 2015 解説CODE FESTIVAL 2015 解説
CODE FESTIVAL 2015 解説
 
CODE FESTIVAL 2015 予選B 解説
CODE FESTIVAL 2015 予選B 解説CODE FESTIVAL 2015 予選B 解説
CODE FESTIVAL 2015 予選B 解説
 
AtCoder Beginner Contest 030 解説
AtCoder Beginner Contest 030 解説AtCoder Beginner Contest 030 解説
AtCoder Beginner Contest 030 解説
 
AtCoder Regular Contest 045 解説
AtCoder Regular Contest 045 解説AtCoder Regular Contest 045 解説
AtCoder Regular Contest 045 解説
 
CODE FESTIVAL 2015 予選A 解説
CODE FESTIVAL 2015 予選A 解説CODE FESTIVAL 2015 予選A 解説
CODE FESTIVAL 2015 予選A 解説
 
AtCoder Beginner Contest 028 解説
AtCoder Beginner Contest 028 解説AtCoder Beginner Contest 028 解説
AtCoder Beginner Contest 028 解説
 

Kürzlich hochgeladen

The_Five_Books_Overview_Presentation_2024
The_Five_Books_Overview_Presentation_2024The_Five_Books_Overview_Presentation_2024
The_Five_Books_Overview_Presentation_2024
koheioishi1
 
TokyoTechGraduateExaminationPresentation
TokyoTechGraduateExaminationPresentationTokyoTechGraduateExaminationPresentation
TokyoTechGraduateExaminationPresentation
YukiTerazawa
 

Kürzlich hochgeladen (7)

次世代機の製品コンセプトを描く ~未来の機械を創造してみよう~
次世代機の製品コンセプトを描く ~未来の機械を創造してみよう~次世代機の製品コンセプトを描く ~未来の機械を創造してみよう~
次世代機の製品コンセプトを描く ~未来の機械を創造してみよう~
 
東京工業大学 環境・社会理工学院 建築学系 大学院入学入試・進学説明会2024_v2
東京工業大学 環境・社会理工学院 建築学系 大学院入学入試・進学説明会2024_v2東京工業大学 環境・社会理工学院 建築学系 大学院入学入試・進学説明会2024_v2
東京工業大学 環境・社会理工学院 建築学系 大学院入学入試・進学説明会2024_v2
 
ゲーム理論 BASIC 演習106 -価格の交渉ゲーム-#ゲーム理論 #gametheory #数学
ゲーム理論 BASIC 演習106 -価格の交渉ゲーム-#ゲーム理論 #gametheory #数学ゲーム理論 BASIC 演習106 -価格の交渉ゲーム-#ゲーム理論 #gametheory #数学
ゲーム理論 BASIC 演習106 -価格の交渉ゲーム-#ゲーム理論 #gametheory #数学
 
The_Five_Books_Overview_Presentation_2024
The_Five_Books_Overview_Presentation_2024The_Five_Books_Overview_Presentation_2024
The_Five_Books_Overview_Presentation_2024
 
2024年度 東京工業大学 工学院 機械系 大学院 修士課程 入試 説明会 資料
2024年度 東京工業大学 工学院 機械系 大学院 修士課程 入試 説明会 資料2024年度 東京工業大学 工学院 機械系 大学院 修士課程 入試 説明会 資料
2024年度 東京工業大学 工学院 機械系 大学院 修士課程 入試 説明会 資料
 
TokyoTechGraduateExaminationPresentation
TokyoTechGraduateExaminationPresentationTokyoTechGraduateExaminationPresentation
TokyoTechGraduateExaminationPresentation
 
生成AIの回答内容の修正を課題としたレポートについて:お茶の水女子大学「授業・研究における生成系AIの活用事例」での講演資料
生成AIの回答内容の修正を課題としたレポートについて:お茶の水女子大学「授業・研究における生成系AIの活用事例」での講演資料生成AIの回答内容の修正を課題としたレポートについて:お茶の水女子大学「授業・研究における生成系AIの活用事例」での講演資料
生成AIの回答内容の修正を課題としたレポートについて:お茶の水女子大学「授業・研究における生成系AIの活用事例」での講演資料
 

AtCoder Regular Contest 030 解説

  • 1. ARC030 解説 解説スライド担当: 三上 和馬 (@kyuridenamida)
  • 3. 問題概要 • n = 6 k = 2 の例 答えは「YES」
  • 4. 解法&考察 • 取り出せる最大の連結成分の数を求めたい • とりあえず頂点を1つ取り除いてみると直線グラフになる 1 2 3 4 5 ● この状態でサイズ1の連結成分を作りまくるのが最適そう したがって端から偶数番目の頂点を取り除いていけばよい ●奇数番目の頂点の数=((n-1)/2を切り上げたもの)が作れる連結成分の最 大数であり,kがこれ以下であればYES,そうでなければNOを出力 •
  • 5. 備考 • 閉路グラフはよく列を扱う問題の派生として出題されます. • 「頂点を1つ○○したら列の問題に帰着できる」等の発想は,方針を 考える上で手助けになるかと思われます.
  • 8. 考察&解法 ●与えられたグラフから出発地点を根とした根付き木を構築する ● ある辺をたどるべきかどうかだが,「辺を辿った先の部分木の頂点が 1つでも宝石を含んでいるときに限り通り,それ以外のとき通らなくて 良い」ということが分かる.部分木が宝石を含んでいるかを判定する 関数を作っておく ● ある辺を使うときは行きと帰りで必ず2回通る ● なので,判定関数で,行く必要のない頂点に遷移しないような再帰関 数を書き,辺をたどる毎に答えに2を加算するプログラムを書けば良 い ●任意のケースで同じ辺を3回以上辿る必要はなく,上記の戦略が最 適.
  • 10. 問題概要 1 a 2 b 4 a 3 b ①a回収 ②移動 ④a回収 ⑤移動 ③移動 ⑥b回収 頂点4からスタート k=3のケース aabを出力
  • 11. 強連結成分分解 ●有向グラフにおいて,相互に行き来できる頂点同士の集合 (強連結成分)を検出し,グループ分けするアルゴリズム ●適当な未訪問の頂点を始点とし,深さ優先探索を行い,帰り がけ順で頂点を列挙することを,未訪問頂点がなくなるまで 繰り返す(トポロジカルソート). ●次に,元のグラフの逆辺グラフを考え,上記で得られた頂点 の順番に,未訪問の頂点を始点とし再度深さ優先探索を繰 り返し行う.このとき,この一度の深さ優先探索でたどり着け る頂点集合が1つの強連結成分となっている
  • 12. 考察&解法 • どこの頂点のアルファベットを回収したかを覚えて探索は無理. • なので,与えられたグラフに対して強連結成分分解を行う • 同じ強連結成分に属する頂点の文字は,自由な順番で取ることがで きる→ある強連結成分にある文字をhoge個使うことを考えたとき,強 連結成分に含まれる文字群をソートした文字列の先頭hoge個を使え ば良いということが分かる • こういった遷移を考えればDAGになり,回収した頂点を覚えなくて良 いDPに帰着できる • DPは「どの強連結成分か」「今何文字か」をキーに辞書順最小の文 字列を格納するものを行う • 強連結成分分解の計算量はO(N+M),文字列比較に最悪O(K)かか るので,動的計画法の計算量はO((NK+M)K)
  • 14. 問題概要 • 長さNの数列Xが与えられ,その後Q個のクエリが飛んでくる.種類は • 1 a b v ― Xの区間 [a,b] に一様に値 v を加える. • 2 a b c d ― Xの区間 [a,b] を,クエリが呼ばれた時点での区間 [c,d] の値に書き換える(b−a=d−c). • 3 a b ― Xの区間 [a,b] の総和を出力. • 1 ≦ N ≦ 200,000 • 1 ≦ Q ≦ 200,000
  • 15. 方針 今回のクエリはタイプ2が曲者なので,そのため 区間に対する操作は平衡二分木等を使うと万能なのでそれを使う.遅 延評価が使えると良い.   segtreeは使えるか…? → 今回はタイプ2があり,使いづらい. ● タイプ2のクエリ([c,d]→[a,b])がやっかい このクエリは,区間[c,d]のコピーを区間[a,b]に上書きするクエリ 平衡二分木のある区間のコピーを作成するには,永続データ構造が 必要そう. 遅延評価機能付き永続型平衡二分木をつくろう.
  • 16. 永続化のための平衡二分木選び • ✖ treap (同じ乱数値を持つノードがたくさん発生するため木が偏る) • ○ 赤黒木 (いける...けど実装重い) • ◎ RandomizedBinarySearchTree (マージ/スプリットするときに乱数を 用いるのでtreapみたいなことは起きない)  ↑計算量解析は難しいらしいが実際偏らず各操作O(log n).実装も 比較的軽くおすすめ!今回はこれを実装する.
  • 17. まずは普通の平衡二分木 • 今回はキーに順序関係が全くないので,「平衡二分探索木」ではなく 「平衡二分木」と呼ぶが,対数計算量実現のためにやってる操作は 一緒. • 区間の問題に対処するためには, – ある位置の頂点を追加する/削除するというinsert/eraseベース の実装 • よりも – ある位置で木を2つに分割する/2つの木を順番を保って併合す るsplit/mergeベースの実装 のほうが都合が良いのでそれを実装していく
  • 18. 各ノードが持つべき情報 ●左の子へのポインタ/右の子へのポインタ ● そのノードの持つ値 ● そのノードを根とした部分木のすべてのノードの値の総和 ● そのノードを根とした部分木のすべてのノードの数 ● そのノードが持つ評価遅延中の値 が持つべき情報 struct T{ T *l,*r; long long v; long long sum; long long lazy; int c; };
  • 19. 作っておくと便利な関数 ●子の情報に基づきノードの情報を更新し,自身のポインタを返す関数 T *update(T *c){ if( c == NULL ) return c; if( c->lazy ){ // 評価遅延しているものを評価し子に伝搬する c->v += c->lazy; if(c->l){ c->l->lazy += c->lazy; c->l->sum += count(c->l) * c->lazy; } if(c->r){ c->r->lazy += c->lazy; c->r->sum += count(c->r) * c->lazy; } c->lazy = 0; } c->c = count(c->l) + count(c->r) + 1; c->sum = sum(c->l) + sum(c->r) + c->v; return c; } int count(T *c){ if( !c ) return 0; else return c->c; } long long sum(T *c){ if( !c ) return 0; else return c->sum; }
  • 20. マージ関数の実装 ● T *merge(T *a,T *b) := aの右の子にbを結合するか,bの左の子にa を結合するかを乱数で決める関数 「aの部分木サイズ>bの部分木のサイズ」のときはだいたいの確率 でaの右の子にbをくっつける」などを繰り返すイメージ ● つなげたあとは,つなげたときに取り除いた子を考慮して再帰 T *merge(T *a,T *b){ if(a == NULL) return b; if(b == NULL ) return a; if( rand() % ( count(a) + count(b) ) < count(a) ){ a = update(a); // たどったノードはとりあえず更新しておく a->r = merge(a->r,b); return update(a); }else{ b = update(b); // たどったノードはとりあえず更新しておく b->l = merge(a,b->l); return update(b); } }
  • 21. スプリット関数の実装 ● pair<T*,T*> split(T *c,int k) := cの部分木を[0,k) [k,n)に分割したもの のそれぞれの根をpairで返す pair<T*,T*> split(T *c,int k){ if(!c) return make_pair(c,c); c = update(c); //たどったノードはとりあえず更新しておく if(k <= count(c->l)){ pair<T*,T*> s = split(c->l,k); c->l = s.second; return make_pair(s.first,update(c)); }else{ pair<T*,T*> s = split(c->r,k - count(c->l) - 1); c->r = s.first; return make_pair(update(c),s.second); } }
  • 22. これらの関数を永続化に対応させるには ● 基本的に全てのノードがconstなものであると考え,子を書き換えた り,遅延情報を伝搬したり,ありとあらゆる変更する際はコピーノー ドを作成し,それを書き換えるという発想に基づく.書き換わってい ないノードは既存のものを使うようにする. ● ここで自身を返す実装が初めて生きてくる ● すると,一度の操作で増えるノードの数はたかだかO(log N)個
  • 23. これらの関数をこの問題に適用していく ● 基本的に,区間に対する操作は, – 区間だけを切り取った木をsplitで作り,根を操作し,そのあとそれをmergeする で対処する. ● たとえば区間[a,b)に対するタイプ1(一様加算)クエリは今の列の[a, b)を取り出して根の遅延評価情報にvを加算し,くっつける ● タイプ2(コピー)クエリに関しては,[0,a) [b,n) そして[c,d) をコピーし た木を作り,[0,a) [c,d) [b,n)の順番でマージする(実際の問題は閉 区間で与えられるので注意) ● タイプ3のクエリは,同様に区間を切り出し根のsumを出力するだけ
  • 24. メモリが溢れることに対する対処 ● 実際は,一回クエリを処理するたびに,ノードが結構生成される ● 適当にやっているとMLEしかねないが,メモリーがやばくなったら一 回列の情報を全てどこかに出力し,永続情報を破棄し再構築する という方法で簡単にごまかすことができる.
  • 25. 計算量 ● 時間計算量 – 一回の操作でO(log N) なので O( (Q+N) log N) – ただし定数倍は重い ● 空間計算量 – O( (N+Q) log N)個のノードがクエリ処理の過程で生成される – 遅延評価するときに,たどったノードに隣接している子ノードの分もコピーするので,こちら も定数倍は重い.前スライドのような方法で対処.