ABC333 F - Bomb Game 2
問題はこちら
問題概要
人の人が一列に並んでいる.先頭の人をの確率で列から取り除き,の確率で列の末尾に移す.人が最後の1人になる確率を求めよ.解説
人の列で先頭から番目の人が最後に残る確率をとします.先頭の人がどうなるかで場合分けを行います.
- 先頭の人が取り除かれるとき:番目だった人は取り除かれ,番目だった人は番目になる.
- 先頭の人が末尾に移されるとき:番目だった人は番目になり,番目だった人は番目になる.
したがっては以下のようになります.
のせいで遷移に循環があります.
と置くとはの一次式で表すことができるので,の昇順にを持ってDPを行うととなるため,となります.今回の制約ではがになることは無いため正しく求めることができます.時間計算量はで,空間計算量はです.
提出プログラム
https://atcoder.jp/contests/abc333/submissions/48609390 (見やすさのため空間計算量O(N^2)で書いてます)感想
競プロ用のツールを作ってみた
この記事は競プロ Advent Calendar 2023 16日目 のために作成されました。
ランダム生成ツール
作ったものはこちらです。https://kanpurin.github.io/RandomGenerator/
使用する場面としては、コンテスト中などで適当なランダムデータが欲しいときなどに自分でランダムケースを作成するのが面倒な場合など?
APIとかはないのでプログラムに組み込んで実験するとかはできません。机上での実験や大きいサイズの入力例などで用いてください。
サーバにデータを送って計算してるわけではないので何度も生成しても大丈夫です。
生成するデータは以下のものになっています。
数列 | (下限)以上(上限)以下の整数を一様ランダムに 個生成(重複を許すか指定可能) (下限) (上限) |
単調増加数列 | (下限)以上(上限)以下の整数からなる長さ の単調増加数列を一様ランダムに生成 |
括弧列 | 長さ の括弧列を一様ランダムに生成 |
ラベル付き木 | 頂点数 のラベル付き木を一様ランダムに生成 |
単純グラフ | 頂点数 辺数 の単純グラフを一様ランダムに生成 |
単純連結グラフ | 頂点数 辺数 の単純連結グラフをランダムに生成 |
素数 | (下限)以上(上限)以下の素数を一様ランダムに生成 (下限) (上限) |
生成手法は以下の記事にまとめています。
使い方
- "N M"を先頭に付ける:頂点数N、辺数Mを先頭に付けます。競プロでよく見るデータの与えられ方です。
- Seed値を設定する:通常は毎回ランダムにデータが生成されますが、これをONにするとSeed値ごとに生成されるデータが決まります。
- グラフを描画する:グラフを可視化します。頂点が多いときはONにしないでください。
N, Mに適切な値を入れて生成ボタンを押すと下のtextareaにデータが生成されます。
値が適切でない場合は生成ボタンがクリックできません。
textareaの右上にあるボタンをクリックするとコピーすることもできます。
Mが大きくとも生成を高速に行うため、textareaに表示するデータ量に制限を付けています。
末尾が`...`となっていますが、コピーボタンによりデータ全体をクリップボードにコピーできます。
グラフ可視化ツール
作ったものはこちらです。https://kanpurin.github.io/GraphVisualizer/
使用する場面としては入力例として与えられたグラフがどのような形なのか見たいとき、可視化されたグラフを編集したいときなど。
グラフ可視化アプリは結構ありますが、図とテキストの相互変換アプリは知らないので作りました。図→テキストはリアルタイムに変換します。
機能としては以下のものがあります。
- ネットワークグラフの可視化:テキストデータを可視化します。
- ネットワークグラフの編集:可視化したグラフに頂点や辺を追加します。
- 二次元グラフの可視化:テキストデータを可視化します。
- 二次元グラフの編集:可視化したグラフに点を追加します。
使い方
詳しくはアプリに説明があるので触ってみたほうが早いです。分かりづらい機能として「頂点固定」がありますが、描画されたグラフは物理モデルによってプルプルしながらいい感じにの見た目に収束するのですが、頂点を固定することによりその頂点は動かなくなります。
これにより物理モデルの影響を受けない直線のグラフなどが作れるようになります。
二次元グラフの可視化は格子点をクリックすると点が打てるというだけです。
開発者募集
GraphVisualizerは開発者を募集してます。バグや追加したい機能がかなりあるので手伝ってください。「READMEに従ってローカルでGraphVisualizerを動かすことができる」ならどなたでも構いません。
GitやJavaScriptに関して全然わからないので教えあいながらやっていけると嬉しいです。
参加したい方は何か適当に機能提案やバグ報告などのIssueを立てるか、既にあるIssueにコメントするか、私のTwitterにメッセージ飛ばすか、この記事にコメントするか、この記事のURLをTwitterで投稿して「興味ある/参加したい」など言ってください。
ABC299 G - Minimum Permutation
問題はこちら
問題概要
以上以下の整数からなる長さの数列の長さ部分列であってが回ずつ登場するもののうち,辞書順最小のものを答えよ.解説
辞書順最小と言えば,前から貪欲に置けるもののうち最も小さいものを順に並べていく方法が考えられます.今回の問題もその方針で考えていきます.以下のサンプルを例に説明していきます.
出来るだけ小さい数字を先頭に選びたいため,まずは答えとなる部分列の先頭の数字がであるかどうかを考えてみます.
すると,以下の図の色付きの数列から答えとなる部分列を選択する必要が出てきますが,が色付きの部分に含まれていないためを部分列の先頭として選ぶことはできません.
このように,それを部分列の先頭として選ぶと,条件を満たす部分列を選ぶことができなくなるようなものは選べません.したがって,それを部分列の先頭として選んでも条件を満たす部分列を選ぶことができなくならないようなものの中でもっとも値が小さいものを貪欲に選べば良いです.
ここで,からの整数それぞれに対して一番後ろに出現するものに色を付けます.
先程の考察から,からまでの中から答えの部分列の先頭要素を選ぶことになります(そうでなければ部分列がすべての数を含むことができない).
ここでは,最小要素のを選び,以降のを選ぶことができなくします.
次の要素は,先程選んだ要素(青)の直後の要素から「まだ選んでない数それぞれに対して一番後ろに出現するもの(橙)」のうち一番先頭にある要素までの中から選びます.
これを繰り返していくことで辞書順最小の部分列を作ることができます.
必要な操作は
- 「まだ選んでない数それぞれに対して一番後ろに出現するもの」のうち一番先頭にある要素を求める
- 区間内のまだ使用していない値のうち最小要素を求める
- 選んだ要素と同じ値をもつ要素を使用不可にする
です.最初の操作はstd::setを用いれば可能,2番目の操作はSegment Treeを用いれば可能,最後の操作はSegment Treeで使用した要素をとすればよいです.
提出プログラム
https://atcoder.jp/contests/abc299/submissions/41075676感想
ランダム生成まとめ
メモ用なので適当に書く.
1以上M以下の整数からなる長さNの数列
長さ,である数列を一様ランダムに生成する.重複あり数列の要素に重複があってもよい場合を考える.
各要素の生成確率をとしてもよい.
重複なし数列の要素が互いに異なる場合を考える.
前から順にまだ使ってない数を等確率で使う.
N個からM個選ぶ組み合わせ
1以上M以下の整数からなる長さNの数列と同じように選ぶ,順番は無視.1以上M以下の整数からなる長さNの単調増加数列
N個からM個選ぶ組み合わせと同じ.総和がMとなる非負整数からなる長さNの数列
個の"〇"と個の"|"を並べるやつ.個の"〇"から"|"にする個を選ぶと考えるとN個からM個選ぶ組み合わせと同じ.
括弧列
バランスのとれた括弧列の一様ランダムな生成.いわゆる構成比法で生成する.括弧列は,以下の図の右下から左上への経路と対応するため経路をランダムに生成することを考える.
毎回,上か右を選択して移動するが,どのような確率で選択すればよいか.
それは,「上に行った場合の残りの経路数」と「右に行った場合の残りの経路数」の比率で遷移の確率が決まる.
これを用いて,上を選択する確率を求めると,
となる.ただし,は現在地から右上の点までの上方向の遷移回数で,は現在地から右上の点までの右方向の遷移回数.
初期値は,長さの括弧列を生成するとして,であり,
上を選ぶと,右を選ぶととなる.
二分木
括弧列をとして再帰させれば1対1対応が取れる.ラベル付き木
prufer列と対応が取れるので1以上M以下の整数からなる長さNの数列を使う.次数列を持つグラフ
与えられた次数列を持つグラフを一様ランダムに生成する.Fast uniform generation of random graphs with given degree sequences
個数上限付き01列
の数が個以下,の数が個以下の長さの01列をできるだけ一様なランダム生成する.また,以下の条件を満たす.の個数をと決めるとN個からM個選ぶ組み合わせとして生成できる.
の個数がとなる確率は,
となる.
が小さければそのまま,が大きければラプラスの定理を用いることで計算できる.
境界はとかでいいかも.
N頂点M辺の連結グラフ
すぐに思いつく方法として,頂点の木をランダム生成した後,辺の数がになるまでランダムに辺を追加するというものがある.しかしこれだと,全域木の数に無視できないほどの差があるため一様ランダムな生成とは言えない.頂点辺の連結グラフを生成する方法はいくつかある.
生成検査法
の辺から本選んでグラフを作る.作ったグラフが連結なら終了.そうでなければまた生成を行う.この方法は一様ランダムに生成できるが,とが近い場合,連結となる確率が低いため生成回数が増大する恐れがある.
全域木法
頂点の木を生成する[ラベル付き木の生成].この木に本の辺を追加する.この方法は高速に生成できるが,一様ランダムにはならない.
構成比法
特定の性質を持つグラフの数に応じた確率を用いることで,比較的速く(それでもまだ遅い),一様ランダムに生成できる.構成比近似法
構成比法では構成比の計算に時間がかかっていた.構成比近似法では構成比の近似値を用いることで高速な生成が可能になる.ほぼ一様ランダムになる.構成比近似法
重複上限Kの数列
以上以下の整数からなる長さの数列であって,同じ数が出現する回数が回以下であるものをランダム生成したい.が小さかったり,かつがある程度大きかったりすると生成検査法で高速に一様ランダムに生成できる.
のときはからまで順に,空いているところから箇所選んで決めていく方法で一様ランダム生成できる.
は使ってない整数から一つ選んで前から埋めていけばよい.
生成検査法で回で条件を満たす確率は,年が日であり,人のうち同じ誕生日の人組が存在しない確率と同じ.(誕生日のパラドックス)
ラベル無し根付き木
https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=b169f11a617bab9395da9f880ce677ca520b2348https://www2.math.upenn.edu/~wilf/website/CombinatorialAlgorithms.pdf
ラベル無し木
https://www2.math.upenn.edu/~wilf/website/Free%20trees.pdf木の総数の近似
N以下の素数
以下の素数を一様ランダムに生成するミラーラビンを使った生成検査法
素数定理から比較的早く生成できることがわかる
全域木
与えられたグラフの全域木を一様ランダムに生成する.http://jakub.tarnawski.org/slides_soda_2015.pdf
ライブラリ
github.comrand_lab_free_trees() | ラベル付き木の一様ランダム生成 |
rand_lab_rooted_trees() | ラベル付け根付き木の一様ランダム生成 |
rand_ulab_free_trees() | ラベル無し木の一様ランダム生成 |
rand_ulab_rooted_trees() | ラベル無し根付き木の一様ランダム生成 |
ABC287 G - Balance Update Query
問題はこちら
問題概要
種類のカードがあり,最初,種類目のカードの得点は,枚数は枚である.以下の形式の個のクエリを順に処理せよ.- 種類目のカードの得点をに変更
- 種類目のカードの枚数をに変更
- 枚のカードを選ぶときの得点の総和の最大値を求める.
解説
各ノードに部分木の得点の総和とカードの枚数の総和を持つBinary Trieを作ると,各クエリで処理することができます.サンプル1を例に挙げて説明します.
初期状態のBinaryTrieは以下のようになります.
最初のクエリでは,枚使った最大の総得点を求めます.
根の右の子の枚数がであることから左の子のカードを使うことはありません.したがって,右の部分木について答えればよいです.
そのさらに右の子を見ると,枚数が枚なので右の子のカードはすべて使い,として左の子に遷移して再帰的に求めていけばよいです.
2番目のクエリや4番目のクエリのような更新では,更新前の得点や枚数を各ノードから引き,更新後の得点や枚数を足していけばよいです.
提出プログラム
https://atcoder.jp/contests/abc287/submissions/38668500感想
ABC285 E - Work or Rest
問題はこちら
問題概要
曜日からなる一週間の各曜日に「平日」か「休日」のどちらかを割り当てる.このとき,曜日の生産量は以下のように表される.- 曜日が「休日」である場合は
- 曜日が「平日」のとき,直前の休日が日前,直後の休日が日後である場合は
一週間当たりの生産量の最大値を求めよ.
解説
曜日の前日は曜日,というように前後に繋がっているので「平日」「休日」の割り当てを決めた際にrotateをしても答えは変わりません.つまり,曜日は必ず休日としてもよいです.
次に,初日が休日で,その後平日が続くようなブロックに分解してみます.
休日日+平日日の全日からなるブロックの生産量は公式解説のと等しいです.これをとします.
すると,この問題は
- 品物の重みを
- 品物の価値を
- 品物の重みの合計の上限を
としたときの個数制限なしナップサック問題となります.
よって,この問題を時間計算量で解くことができます.
提出プログラム
https://atcoder.jp/contests/abc285/submissions/38167895 C++https://atcoder.jp/contests/abc285/submissions/38169128 Python
N = int(input()) A = list(map(int,input().split())) V = [0] * (N+1) DP = [0] * (N+1) for i in range(2,N+1): V[i] = V[i-1] + A[i//2-1] for j in range(i,N+1): DP[j] = max(DP[j], DP[j-i] + V[i]) print(DP[N])