AtCoder Regular Contest 026 解説

49
AtCoder Regular Contest 026 解説 AtCoder株式会社 2014/6/28 1

description

AtCoder Regular Contest 026 解説

Transcript of AtCoder Regular Contest 026 解説

Page 1: AtCoder Regular Contest 026 解説

AtCoder Regular Contest 026 解説

AtCoder株式会社

2014/6/28 1

Page 2: AtCoder Regular Contest 026 解説

A問題ダイナミックなポーズ

Page 3: AtCoder Regular Contest 026 解説

問題概要• 高橋君は1問の問題を解くのに・普段:A分かかる・ダイナミックなポーズをとりながら:B分かかる

• 体力の消耗が激しいので、ダイナミックなポーズをとりながら解ける問題は5問まで。

• N問の問題を解くのにかかる最短時間は?• 1 ≦ N ≦ 10, 1 ≦ B < A ≦ 60

1/4

Page 4: AtCoder Regular Contest 026 解説

解法

• B < Aなので、可能な限りダイナミックなポーズをとりながら解いたほうが良い。

• じゃあ答えは、5*B+(N-5)*A?(5問をダイナミックなポーズをとりながら解き、残りの問題を普段通りに解く)

→ それは間違い

2/4

Page 5: AtCoder Regular Contest 026 解説

解法• 5*B+(N-5)*A はダメ。• Nが5より小さい時はダイナミックなポーズはN回しかしなくて良い。

•というようにNが5より大きいか小さいか、つまり、5回の上限までダイナミックなポーズをとるかどうかで場合分けをしてあげればいい。

if(N < 5) answer = N*B;else answer = 5*B+(N-5)*A;

3/4

Page 6: AtCoder Regular Contest 026 解説

別のやり方

• のようにダイナミックなポーズをとる回数を計算してから答えを計算するというような方法も考えられます。

num_dynamic = min(N,5);answer = num_dynamic*B + (N-num_dynamic)*A;

4/4

Page 7: AtCoder Regular Contest 026 解説

ARC026解説

B:完全数

Page 8: AtCoder Regular Contest 026 解説

B-問題概要

● 与えられた整数 が完全数か、不足数か、過剰数か、判定せよ

● 部分点1 ● 部分点2 

N

1≤N≤105

1≤N≤1010

Page 9: AtCoder Regular Contest 026 解説

B-基本的発想

● 約数を全て調べて、総和を求めて、元の数字との大小を調べたい

● 約数を出来るだけ速く全列挙するのが目標

● 「自分以外の約数」というのが少し厄介● 自分を含めた約数の総和を求めて自分の2倍と

の大小を比べれば面倒な処理はしなくて良い

Page 10: AtCoder Regular Contest 026 解説

B-約数の全列挙1

● 整数 の約数は 以下なので、 以下の全ての整数について、それが の約数かどうかを調べれば良い

● 計算量は– 部分点1が得られる

N N NN

N

O(N )

Page 11: AtCoder Regular Contest 026 解説

B-約数の全列挙2

● が の約数ならば、 も の約数● か の片方がわかれば、もう片方もすぐに

分かる。– 小さい方だけを調べれば良い

● となる条件は● √ 以下の約数を調べるだけで良い● 計算量は (√ )

– 満点が得られる

AA

A≤N /A

AN N /A NN /A

A2≤N

N

NO

Page 12: AtCoder Regular Contest 026 解説

B-注意点

● が平方数の時 となる が存在する– 誤って を2回足さないように気をつけなければな

らない

● 小課題2の入力が明らかにint型に収まらないので、C等の言語を使っている方は気をつけましょう

N A=N /A AA

Page 13: AtCoder Regular Contest 026 解説

ARC024解説

C:蛍光灯

Page 14: AtCoder Regular Contest 026 解説

C-問題概要

● 長さがLの廊下にN個の蛍光灯がある● i番目の蛍光灯はc_iの費用で点ける事ができる● i番目の蛍光灯をつけると西からの距離がl_i以上

r_i以下の範囲が照らされる● 廊下全体を照らすための最小費用を求めよ

● 1 ≦ N ≦ 100,000● 1 ≦ L ≦ 100,000● 1 ≦ c_i ≦ 100,000

Page 15: AtCoder Regular Contest 026 解説

C-部分点1

● 蛍光灯をl_iが小さい順にソートする– 以降iにはこのソート後のインデックスを採用する

● dp[i][j] = i番目以前の蛍光灯を使って西から距離jのところまでの全体を照らす費用の最小値– という値を動的計画法で求める

● もしjがl_iとr_iの間にあるならば

dp[i][j] = min{dp[i-1][k] + c_i (kはl_iとr_iの間)}

ないならば

dp[i][j] = dp[i – 1][j]

という漸化式が成り立つ

Page 16: AtCoder Regular Contest 026 解説

C-DPの更新の仕方

● l_iとr_iの間のjに対して

dp[i][j] = min{dp[i-1][k] + c_i (kはl_iとr_iの間)}

● kをl_iからr_iまで順番に動かすときに、今まで通過したdp[i-1][k]の最小値を更新していく– dp[i][k] = その最小値 + c_iとなる

● dp[i]を全て求めるのにO(L)● iはN種類なので、合計でO(NL)

– 部分点1(N,L≦3,000)が得られる

Page 17: AtCoder Regular Contest 026 解説

C-DP擬似コードfor(int i = 0;i < n;i++){ //dp[i-1][k]の最小値を保存する変数tmpmin int tmpmin = 99999999; for(int j = 0;j <= L;j++){

if(j < l[i] || k > r[j]){ dp[i][j] = dp[i - 1][j]; } } for(int k = l[i];k <= r[i];k++){ dp[i][k] = min(dp[i][k], tmpmin + c[i]); tmpmin = min(tmpmin, dp[i][k]); }}

Page 18: AtCoder Regular Contest 026 解説

C-考察

● 先ほどのDPは2次元配列を使っているが、dp[i]はdp[i – 1]しか参照しない– 1次元配列で管理することができる

● 新しいdp配列– dp[i] = 西端から距離iのところまでの全体を照らす

のに必要な費用の最小値

Page 19: AtCoder Regular Contest 026 解説

C-考察

● 実際に費用が最小になる蛍光灯の置き方を考えてみる

● 必ずr_iがとなりの蛍光灯の区間(両端含む)と重なる– dpではr_iの位置のみ更新すれば良い

Page 20: AtCoder Regular Contest 026 解説

C-擬似コード2

for(int i = 0;i < n;i++){ //dp[i]の最小値を保存する変数tmpmin int tmpmin = 99999999; for(int k = l[i];k <= r[i];k++){ tmpmin = min(tmpmin, dp[i][k]); }

dp[r[i]] = min(dp[r[i]], tmpmin + c[i]);}

Page 21: AtCoder Regular Contest 026 解説

C-高速化

● 操作の種類は– dp[r[i]]の更新

– dp[l[i] ~ r[i]]のなかの最小値を求める

● これはRMQなのでセグメントツリーがうまく利用できる

Page 22: AtCoder Regular Contest 026 解説

C-擬似コード3

for(int i = 0;i < n;i++){int tmp = getmin(l[i], r[i])//dp[l[i]~r[i]]の最小値をsegtree等で求めるsetnum(r[i], min(r[i], tmp + c[i]));//dp[r[i]]の値を更新

}

Page 23: AtCoder Regular Contest 026 解説

C-入出力例3図解

Page 24: AtCoder Regular Contest 026 解説

C-入出力例3図解

Page 25: AtCoder Regular Contest 026 解説

C-入出力例3図解

Page 26: AtCoder Regular Contest 026 解説

C-入出力例3図解

Page 27: AtCoder Regular Contest 026 解説

C-入出力例3図解

Page 28: AtCoder Regular Contest 026 解説

C-入出力例3図解

Page 29: AtCoder Regular Contest 026 解説

C-入出力例3図解

Page 30: AtCoder Regular Contest 026 解説

C-入出力例3図解

Page 31: AtCoder Regular Contest 026 解説

C-入出力例3図解

Page 32: AtCoder Regular Contest 026 解説

C-入出力例3図解

Page 33: AtCoder Regular Contest 026 解説

C-高速化

● 各蛍光灯についてO(logL)でdp[r[i]]を更新する● 計算量はO(NlogL)

– 満点が得られる

Page 34: AtCoder Regular Contest 026 解説

C-参考

● セグメントツリーに似たデータ構造でBITというものがある– 比較的楽に実装できる

● BITは基本的にRMQはできないが、いくつかの制約のもとで処理できる

1.値の更新は、もとより値が小さくなるもののみ

2.求める範囲の左端は0でなければならない

● 今回1つめの条件は満たしている● 2つめの条件を満たすようにすればBITを使うこ

とができる

Page 35: AtCoder Regular Contest 026 解説

C-参考

● 実はmin(l[i], r[i]) は min(l[i], L)としてもよい– 予めソートしてあるので、i番目の蛍光灯を見てい

る時、l[j] > l[i]となる蛍光灯jはまだ処理されていない

– l[i] ~ Lと重なっている蛍光灯は、l[i] ~ r[i] と重なっている蛍光灯のみであり、その他余計な蛍光灯は重なっていない

● よってL側を片端とするBITでACすることができる

● 計算量はセグメントツリーと同様

Page 36: AtCoder Regular Contest 026 解説

D問題道を直すお仕事

Page 37: AtCoder Regular Contest 026 解説

問題概要

• N頂点M辺のグラフが与えられる• 各辺はC,Tの二つの値を持っている• グラフが連結になるように辺を選ぶとき、Cの総和/Tの総和 の最小値は?

• 1 ≦ N,M ≦ 10^4, 1 ≦ Ci,Ti ≦ 10^6

1/13

Page 38: AtCoder Regular Contest 026 解説

20点解法 (M ≦ 16)

• それぞれの辺を修理するかどうかを全て試す• 各辺につき、する/しないの2通りがあるので全部で2^M通りのパターンがあり得る

• グラフが連結になるような辺の選び方のうち、Cの和/Tの和が最小となるものが答え

2/13

Page 39: AtCoder Regular Contest 026 解説

連結判定 方法1

• 適当な頂点を選び、その頂点を含む連結成分の大きさをDFSなどで計算し、それがNであれば連結

bool used[N]; // falseで初期化int dfs(int v){ if(used[v]) return 0; used[v] = true; int count = 1; for(Edge e : vから出ている辺){ count += dfs(e.to); } return count;}

連結成分の大きさを求める例:

3/13

Page 40: AtCoder Regular Contest 026 解説

連結判定 方法2• 以下のアルゴリズムのように、連結成分を集合として管理する

• これを実現するためのデータ構造として、Union Find 木を使う

各頂点に対し、その頂点のみを含む集合を作るfor(Edge e : 修理することにした辺){e.fromが属する集合とe.toが属する集合を合併する

}全ての頂点が同じ集合に属しているかをチェックする

4/13

Page 41: AtCoder Regular Contest 026 解説

Union Find 木• 以下の2つの操作が出来るデータ構造・ある要素がどの集合に属しているかを調べる・2つの集合を合併する

• 要素を頂点とした木構造で集合を管理する• 各頂点は親への辺を持っておく• 根の要素をその集合の代表と考える• 詳細は以下のスライドが分かりやすいと思いますhttp://www.slideshare.net/iwiwi/ss-3578491

5/13

Page 42: AtCoder Regular Contest 026 解説

Union Find 木• 実装例 (ならし計算量O(α(N)))

int parent[N];void init(){ for(int i = 0; i < N; ++i) parent[i] = -1;}int root(int x){ if(parent[x] == -1) return x; return parent[x] = root(parent[x]);}void unite(int x, int y){ x = root(x); y = root(y); if(parent[x] < parent[y]) swap(x,y); if(parent[x] == parent[y]) parent[x]--; parent[y] = x;}

6/13

Page 43: AtCoder Regular Contest 026 解説

20点解法 (M ≦ 16)• 辺の選び方を全て試すためにはビット演算を利用すると書きやすい

• あわせて計算量はO(2^M * N)となり、この部分点を得ることが出来ます

for(int i = 0; i < 1<<M; ++i){ // 選び方を全て試すfor(int j = 0; j < M; ++j){ if((i>>j&1) == 1) j本目の辺は今選ばれている}

}

7/13

Page 44: AtCoder Regular Contest 026 解説

満点解法 (M ≦ 10000)

• 答えで二分探索• ΣCi / ΣTi ≦ x となるような最小のxが答え• 小数の二分探索は収束判定が難しいので、50回だけ探索するというようにすると良い。

8/13

Page 45: AtCoder Regular Contest 026 解説

二分探索

• ΣCi / ΣTi ≦ x を式変形をすると・・・• ΣCi / ΣxTi ≦ 1 → ΣCi ≦ ΣxTi → ΣCi-xTi ≦ 0• 辺ごとに独立して計算できるようになる!

9/13

Page 46: AtCoder Regular Contest 026 解説

二分探索

• ΣCi-xTi ≦ 0 を満たすような辺の選び方があるかどうかを判定したい

• Ci-xTi ≦ 0となる辺は全て選んで良い• グラフが連結になるように、残ったCi-xTi > 0の辺からいくつかの辺を選ぶ。→ 最小全域木問題のアルゴリズムが使えそう

10/13

Page 47: AtCoder Regular Contest 026 解説

クラスカル法

• クラスカル法:最小全域木を求めるアルゴリズム

連結成分の管理にはUnion Find 木を使うと良い

answer = 0for(Edge e : 辺をコストの昇順に){if(e.fromとe.toが違う連結成分に属している){ answer += e.cost e.fromの連結成分とe.toの連結成分を合併する}

}

11/13

Page 48: AtCoder Regular Contest 026 解説

最小コストを求める

• 今回の問題は最小全域木問題とは少しだけ違いますが、似たようなアルゴリズムで解くことが出来ます

answer = 0for(Edge e : 辺をコストの昇順に){ // コスト = c-xtif(e.fromとe.toが違う連結成分に属している または e.cost < 0){ answer += e.cost e.fromの連結成分とe.toの連結成分を合併する}

}

12/13

Page 49: AtCoder Regular Contest 026 解説

満点解法 (M ≦ 10000)

• 二分探索で探索する回数をKとすると、• 計算量はO(K M log M)となり、Kが100程度でも満点を得ることが出来る。

13/13