Codeforces Round #630 (Div. 2) G. No Monotone Triples
お題箱より。けっこう難しかった…
問題概要
数列 が与えられる。この数列の長さ 以上の部分列が「free from monotone triples」であるということを、「その部分列からさらに長さ の部分列をどのように取っても、広義単調増加にも広義単調減少にもならない」ことと定義する。
以下の 個のクエリに答えよ。
- 整数 が与えられる。 の部分列であって「free from monotone triples」であるものが存在すれば、その中で最長のものを1つ求めよ。存在しない場合は を出力せよ。
制約
- 各クエリについて
解法
手やプログラムで実験してみると、長さ 以上の数列は必ず単調な長さ の部分列を含むことが分かります。よって、条件を満たす長さ の部分列を探すこと、長さ の部分列を探すことをそれぞれ行えば良いです。
長さ の場合
※長さ について解くだけならもう少し楽な方法があるのですが、後の長さ の解法に繋がるような方法で書きます。
作りたい つ組を と表記します。長さ で条件を満たす部分列は、
- が の両方より真に大きい。(
^
の形) - が の両方より真に小さい。(
v
の形)
のいずれかを満たすものです。前者を例に解説します。
目標は各要素 について、その要素を としたときに が最も近くにある つ組を(もし存在すれば)求めることです。クエリで指定された区間になるべく含まれて欲しいので、これらだけを考えれば十分です。
を左から順番に見ていきながら、スタックで以下のような列を管理します。(実際にはランダムアクセスしたいので vector
等でやるのが良いです)
つまり を起点として「自分より左側にあって、値が自分以上である一番近い要素」を連鎖的に取っていき、そのインデックスを管理したものです。
を右端 とした つ組を見つける時には、以下の手順を踏みます。
- まずこのスタックの中で、 より大きい一番右側の要素を探し、これを とする。
- より左側にあって最も近い「スタックに含まれていない要素」を探し、その要素を とする。
- より右側にあって最も近い「スタックに含まれている要素」を とする。(これは とは限らないことに注意)
1と3の手順はスタックに対して二分探索すれば良いです。2の手順では、1度スタックに入って追い出された要素のインデックスをBITなどで管理すれば二分探索で求めることができます。
これが最適であることは、
- と の間に必ず両者より大きい要素が必要なので、 をこの手順で見つけたものより右側に取ることはできない。
- この手順で を見つけた場合、必ず として利用可能な要素が存在する。
という2点を確認することで示せます。
これで考えるべき つ組を抽出できました。各クエリについての判定は、 つ組とクエリの区間をともに右端ごとに分類し、 を左から順番に見ていきながら
- 既に右端を通り過ぎた(利用できる) つ組のうち、左端が最も右側にあるものを管理する。
- 今見ている場所を右端とするクエリについて、クエリの区間が上記の つ組を管理していれば、これを採用する。
とすれば良いです。
長さ の場合
作りたい つ組を と表記します。実験をすると、 つ組が条件を満たすためには
- がともに、 つ組の中で最大値でも最小値でもないこと
が必要十分であると分かります。
先ほどと同様に、各要素 について、その要素を としたときに が最も近くにある つ組を(もし存在すれば)求めることを目指します。先ほどは左上に伸びるスタックを管理しましたが、今後は左下に伸びるスタックも同様に管理します。
手順も先ほどとほぼ同様です。
- まず つのスタックの中に対して以下の値をそれぞれ求め、より左側にあるほうを とする。
- 左上に伸びるスタックの中で、 より大きい一番右側の要素。
- 左下に伸びるスタックの中で、 より小さい一番右側の要素。
- より左側にあって最も近い「どちらのスタックにも含まれていない要素」を探し、その要素を とする。
- それぞれのスタックにおいて、 より右側にあって最も近い「スタックに含まれている要素」を、並び順に とする。
ここでスタックが つあるので、手順2では探索対象を「どちらのスタックからも追い出された要素」とすると良いです。
クエリについての判定も同様にできて、長さ の時に解が見つかっているクエリも長さ の解で上書きすれば良いです。
ACコード
Submission #77240758 - Codeforces
実装がごちゃごちゃしているのでコメントをマシマシにしています。