より制約を弱くして一方のみが下に凸だとした場合も SMAWK Algorithm を使えば畳み込む数列の長さの和に対して線形時間で求まるんですが,両方ともが下に凸という制約を課した場合はかなり簡単に,かつ定数倍もかなり軽いアルゴリズムで求まることが分かったのでこの記事を書きました.
問題
長さ の数列 と長さ の数列 が与えられます.このとき,すべての に対して
を求めてください.ただし, はともに下に凸であるとします.つまり,次の 2 つの条件を満たします.
条件:
- 全ての に対して
- 全ての に対して
解法
各 に対して, の下で を最小化する をそれぞれ と表す.ただし,そのような の組が複数ある場合は,適当に一つだけ選ぶことにする.
まず, の場合を考える.このときは,明らかに である.
続いて, の場合を考える.このとき,実は または の 2 通りを試すだけでよい.これは,以下のような場合分けから従う.
- のとき
- のとき
この事実を用いることで, に対して を 時間で求めることが出来る.
実装
std::vector<int> convolve(const std::vector<int> &a, const std::vector<int> &b) { constexpr int INF = std::numeric_limits<int>::max(); int n = a.size(); int m = b.size(); if (n == 0 or m == 0) return {}; std::vector<int> c(n + m - 1); c[0] = a[0] + b[0]; int i = 0, j = 0; for (int k = 1; k <= n + m - 2; ++k) { int x = i + 1 == n ? INF : a[i + 1] + b[j]; int y = j + 1 == m ? INF : a[i] + b[j + 1]; if (x <= y) { c[k] = x; ++i; } else { c[k] = y; ++j; } } return c; }
おまけ (片方のみが下に凸な場合)
以下, は任意の数列, は下に凸な数列としても一般性を失いません (逆なら swap すればよいので).
行列 を次の図のように構築します.ただし, を陽に持った時点で計算量が となってしまうので,陰に持ちます (つまり,必要になった時に計算する).
このとき,結論から言えば, は totally monotone であることが示せます.従って,SMAWK Algorithm を用いることですべての に対する を で求めることができ,同時に も求まります.SMAWK Algorithm に関しては,参考文献 [1] が分かりやすいので,詳細は省かせて頂きます (不等号の誤植があった気はしますが).
SMAWK Algorithm の説明を省いた代わりに,ここでは が totally monotone であることを確認しておきましょう.これは,任意の 部分行列が monotone であることが言えればよいです.
ここで,上のような 行列に対して,次のいずれかを満たす場合に monotone であるといいます.
- かつ
- かつ
つまり, を満たす であって,部分行列 に関して
- かつ
- かつ
となるようなものが存在しないことを言えればよいです.まず, に対して適切な値を入れておくことで, を含むような 部分行列を monotone にできます 1.従って,以下では部分行列が を含まない場合 ( が配列外参照を起こさない場合) を考えます.
が配列外参照を起こさないという仮定の下では,部分行列は次のように表すことが出来ます.
では,以下のいずれかが成立するという仮定を置いて,矛盾を導きます.
- かつ
- かつ
方針としては, に注意しながら, が下に凸であるという性質を活かせるように式変形を頑張るだけです.
以上より,矛盾が示せたので は totally monotone であることが分かりました.
参考文献
[1] http://web.cs.unlv.edu/larmore/Courses/CSC477/monge.pdf
-
右上の区画では に対して単調増加な値を,左下の区画では に対して単調減少な値を入れるとよいです↩