アルゴリズム競合の特別な分析(12):DP最適化(2)-勾配(凸包)最適化

このシリーズは、このアルゴリズムの教科書の拡張です:「Introduction to Advanced Algorithm Contest」(Jingdong Dangdang)Tsinghua University Press
ご意見がございましたら、お問い合わせください:(1)QQ Group、567554289;(2)Author QQ、15512356

  DPの状態方程式のクラスがあります。たとえば、
    \(dp [i] = min \ {dp [j] -a [i] * d [j] \} \)   \(0≤j<i、d [j]≤d [J + 1]、[
I]≤A [I + 1] \)   の両方のAが存在することを特徴とする\(I \)が持っている\(J \)用語\([I] * Dは [ j] \)
  プログラミング時に、単に\(i \)\(j \)をループする場合、複雑さは\(O(n ^ 2)\)です。
  勾配最適化(凸包トリック、凸包最適化)により、時間の複雑さは\(O(n)\)に最適化されます。
  スロープ最適化のコアテクノロジーは、スロープ(凸包)モデルと単調キューです。

1.状態方程式を平面傾斜問題に変換する

  固定\(i \)に対して\(j \)が変更されると、方程式\(dp [i] \)の最適値を見つけますしたがって、\(i \)に関する部分は固定値と見なすことができます。\(j \)の部分は変数とみなされます。\(分\)除去され、式に:
    \(DP [J] = A [I] * D [J] + DP [I] \)
  観察の便宜のために、その結果:\(Y = DP [J] \ )\(X = d [j] \)\(k = a [i] \)\(b = dp [i] \)、方程式は次のようになります:
    \(y = kx + b \)
  勾配の最適化の数学モデルは、状態伝達方程式を平面座標系の直線に変換することです:\(y = kx + b \)その中で:
  (1)変数\(x \)\(y \)および\(j \)は関連しており、\(y \)だけ\(dp [j] \)を含みます\((x、y)\)は、問題の中で可能な決定です。
  (2)勾配\(k \)と切片\(b \)は\(i \)関連しており、\(b \)のみ\(dp [i] \)が含まれています最小の\(b \)には、最小の\(dp [i] \)が含まれます。これは、状態方程式の解です。
  勾配最適化を適用するための2つの条件に注意してください。\(x \)および\(k \)は単調に増加します。つまり、\(x \)\(j \)とともに増加し\(k \)\に従います(i \)増分および増分。

2. dp [i]を見つける

  固定\(i \)のとき、最初に\(dp [i] \)を見つけることを検討してください以下のため\(I \)定数であり、その後の傾き\(K = [I] \)は一定とみなすことができます。場合\(J \)\(0≤j<I \)ときの変化\(j_r \) 点を生成する(V_R =(X_R、Y_r)\)\直線のこの時点で、\ (y = kx + b_r \)\(b_r \)は切片です。図1に示すように。

図1点(x、y)を通る線

  ため(0≤j<I \)\全部で\(J \) それらの対応する点が平面上に描かれていると、点に対応する線の傾き\(K = [I] \)は同じであり、切片\(b \)のみ異なります。すべてのこれらの点において、点\(V '\)切片線形最小場合(\ \ b)は'、算出した'(\ B)\ので、(\ B)\'含有(DP [\ i] \)、次に最適な\(dp [i] \)が計算されます。図2に示すように。

図2最も有利なv 'を通る直線

  最も有利な\(v '\)を見つける方法は「下凸シェル」を使用。
  前述のように、\(x \)は単調に増加します。つまり、\(x \)\(j \)とともに増加します。図3(1)では4つの点が示され、それらの\(x \)座標は増加しています。

(1)元の画像(2)点3を削除(3最適なv点を見つける)
図3下の凸型シェルを使用して最高の利点を見つける

  図3(1)の1,2,3は「下凸包」を構成しており、「下凸包」の特徴は、線分12の傾きが線分23の傾きよりも小さいことである。2、3、4は「上部凸シェル」を構成します。上部の凸包の中間点3を通る直線の切片bは、同じ勾配で2または4を通る直線の切片よりも小さくなければならないため、点3は最善の利点ではないので、削除してください。
  「上凸包」を削除すると、図3(2)が得られ、残りのすべての点が「下凸包」の関係を満たします。一番の利点は、「下凸シェル」にあります。たとえば、図3(3)では、これらのポイントを\(k \)の傾きを持つ直線カットします。ラインセグメント12の傾きが\(k \)より小さく、24の傾きが\(k \)より大きい場合、ポイント2は「下凸シェル」の最大のメリット。
  上記の操作は、単調なキュープログラミングを使用するのに非常に便利です。
  (1)キューに「下に凸の船体」を維持した状態でチームに入る。つまり、2つの連続するポイントごとに構成される直線であり、傾きは単調に増加している。新しいポイントがキューに入力されるとき、キュー内のポイントと一緒に「下部凸包」を形成できることを確認してください。たとえば、キュ​​ーの最後の2つのポイントは\(v_1 \)\(v_2 \)であり、キューに追加される新しいポイントは\(v_3 \)です。比較\(V_1 \)、\ (V_2 \)、\ (V_3 \) ラインを見て\(v_1v_2 \)\(v_2v_3 \)の傾きがインクリメントされているかどうか、もしそうであれば、\(V_1 \)、\ (V_2 \)\(v_3 \)は「下部凸包」を形成します。勾配が増加しない場合は、\(v_2 \)が正しくないことを意味し、チームの末尾から跳ね返ります。次に、キューの末尾と\(v_3 \)の2つのポイントを比較し続けます。繰り返します。上記の\(v_3 \)までの操作でチームに入ることができます。上記の操作の後、キュー内のポイントは大きな「下部凸包」を形成し、各2ポイントによって形成される直線は増加する勾配を持ち、キューは単調なキューのままです。
  (2)待ち行列から抜け出し、最良の利点を見つけます。チームの先頭にある2つのポイントは\(v_1 \)\(v_2 \)です。ラインセグメント\(v_1v_2 \)の勾配が\(k \)よりも小さい場合\(v_1 \)が最善の利点ではないことを意味します。 、傾斜が\(k \)を超えるまでチームの先頭にある2つの新しいポイントを比較し続け、チームの先頭にあるポイントが最も有利な\(v '\)です。

3.すべてのdp [i]を見つける

  上記は\(dp [i] \)、複雑さ\(O(n)\)を取得しましたすべての場合には\(I \) 各そのような要求\(DP [I] \)は、全体的な複雑性はまだある(O(N ^ 2)\ \) 、および計算の複雑さは変わりません。最適化された方法はありますか?\(i_1 \)が
  小さいほど、対応する点は{ \(v_0、v_1、...、v_ {i1} \) }になり、\(i_2 \)が大きいほど多くの点に対応します{ \(v_0、v_1、...、v_ {i1}、...、v_ {i2} \) }、これは\(i_1 \)のすべてのポイントを含みます。\(i_1 \)の最大の利点を探す場合は、{ \(v_0、v_1、...、v_ {i1} \) }を確認する必要があります。i2の最大の利点を探す場合は、{ \(v_0、v_1 ,.を確認する必要があります。 ..、v_ {i1}、...、v_ {i2} \) }。ここでは重複チェックが行われ、これらの重複を回避できます。これは、最適化できる場所であり、最適化のために「下凸シェル」を使用します。   (1)各\(i \)\(k_i = a [i] \)に対応する勾配は、制約に従って異なります
\(a [i]≤a [i + 1] \)\(i \)が増加すると、勾配が増加します。

図4複数のiに対応する線

  (2)前述のように、\(i_1 \)の最良の点を見つけたら、いくつかの点、つまり\(k_ {i1} \)より小さい勾配を持つ点を削除できますこれらの削除されたポイントは、背面の\(i_2 \)が大きい場合、傾き\(k_ {i2} \)も大きいため、削除する必要があります。
  (1)と(2)の説明によると、最適化方法は次のとおりです:すべての\(i \)に対して、単調なキューを使用してすべてのポイントを統一された方法で処理します;小さい\(i_1 \)によって削除されたポイントは単調です跳ね返ると、後ろ大きな\(i_2 \)はそれらを処理しなくなります。
  各ポイントは単調なキューに1回だけ入るため、全体の複雑度は\(O(n)\)です。
  次のコードは、上記の操作を示しています。

//q[]是单调队列,head指向队首,tail指向队尾,slope()计算2个点组成的直线的斜率
for(int i=1;i<=n;i++){ 
    while(head<tail && slope(q[head],q[head+1])<k)  //队头的2个点斜率小于k 
        head++;                                     //不合格,从队头弹出
    int j = q[head];   //队头是最优点
    dp[i] = ...;       //计算dp[i]
    while(head<tail && slope(i,q[tail-1])<slope(q[tail-1],q[tail]))   //进队操作
        tail--;        //弹走队尾不合格的点
    q[++tail] = i;     //新的点进队列
}

  上記のコードの理解を深めるために、特別なケースを考慮してください:キューに入るポイントはすべて「下部凸包」機能に準拠し、これらのポイントによって形成される直線の勾配はすべての勾配\(k_i \)よりも大きい場合、結果は次のようになります:チームヘッドはそれは排出され、チームに入るポイントは排出されず、チームヘッドは\(n \)再利用されます。

図5特別なケース

4.例

  典型的な例を以下にサンプル質問とともに示します。


HDU 3507 記事の印刷http://acm.hdu.edu.cn/showproblem.php?pid=3507
トピックの説明:Nワードを含む記事を印刷します。i番目のワードの印刷コストはCiです。1行にkワードを印刷するコストは、Mが定数であることです。コストを最小限に抑えるために記事を整理する方法は?
入力:多くのテストケースがあります。各テストケースでは、最初の行に2つの数値NとMがあります(0≤n≤500000、0≤M≤1000)。次に、次の2からN + 1行にN個の番号があります。入力はEOFで終了します。
出力:記事を印刷するための最小コストを示す数値。
入力例
5 5
5
9
5
7
5
出力例
230


  タイトルの意味は次のとおりです。\(N \)数と定数\(M \)があり、\(N \)数はいくつかの部分に分割され、各部分の計算値はこれらの部分の合計の二乗と\(M \)、合計計算値はすべての部品の計算値の合計であり、最小の合計計算値を見つけます。ための(N \)\大きい、\(O(N ^ 2)\)アルゴリズムタイムアウト。
  セット\(DP [I] \)の前に出力を表す\(I \) 単語の最小コスト、DP移動式
    \(DP [I] =分\ {DP [J] +(SUM [I] -sum [ j])2 + M \} \)   \(0 <j <i \)
  ここで、\(sum [i] \)は最初の\(i \)合計を表します。
  次は、DP方程式を\(y = kx + b \)に書き換えます。最初に方程式を展開します:
    \(dp [i] = dp [j] + sum [i] * sum [i] + sum [j] * sum [j] -2 * sum [i] * sum [j] + M \ )
  アイテムの移動:
    \(dp [j] + sum [j] * sum [j] = 2 * sum [i] * sum [j] + dp [i] -sum [i] * sum [i] -M \)
  コントロール\(y = kx + b \)があります。
  \(y = dp [j] + sum [j] * sum [j] \)\(y \)は\(j \)にのみ関連しています。
  \(X = 2×SUM [J] \)、\ (X \)のみと\(J \)について、とを有する\(J \)がインクリメントインクリメントされます。
  \(k = sum [i] \)\(k \)は\(j \)のみ関連し\(i \)が増加するにつれて増加します。
  \(b = dp [i] -sum [i] * sum [i] -M \)\(b \)は iにのみ関連し、\(dp [i] \)を含みます
  コードを以下に示します。

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 500010;

int dp[MAXN];   
int q[MAXN];      //单调队列
int sum[MAXN];

int X(int x){ return 2*sum[x]; }
int Y(int x){ return dp[x]+sum[x]*sum[x]; }
//double slope(int a,int b){return (Y(a)-Y(b))/(X(a)-X(b));} //除法不好,改成下面的乘法
int slope_up  (int a,int b) { return Y(a)-Y(b);}   //斜率的分子部分
int slope_down(int a,int b) { return X(a)-X(b);}   //斜率的分母部分

int main(){
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        for(int i=1;i<=n;i++)  scanf("%d",&sum[i]);
        sum[0] = dp[0] = 0;
        for(int i=1;i<=n;i++)  sum[i]+=sum[i-1];

        int head=1,tail=1;      //队头队尾
        q[tail]=0;
        for(int i=1;i<=n;i++){
            while(head<tail &&
                  slope_up(q[head+1],q[head])<=sum[i]*slope_down(q[head+1],q[head])) 
               head++;           //斜率小于k,从队头弹走

            int j = q[head];     //队头是最优点
            dp[i] = dp[j]+m+(sum[i]-sum[j])*(sum[i]-sum[j]);    //计算dp[i]

            while(head<tail && 
                              slope_up(i,q[tail])*slope_down(q[tail],q[tail-1])
                           <= slope_up(q[tail],q[tail-1])*slope_down(i,q[tail]))
                tail--;          //弹走队尾不合格的点
            q[++tail] = i;       //新的点进队尾
        }
        printf("%d\n",dp[n]);
    }
    return 0;
}

5.演習

  (1)Luogu P3195おもちゃの梱包https://www.luogu.com.cn/problem/P3195
  DP式:\(dp [i] = min \ {dp [j] +(sum [i] + i− sum [j] −j−L−1)^ 2 \} \)
  
  (2)Luogu 4072 SDOI2016ジャーニーhttps://www.luogu.com.cn/problem/P4072
  2次元勾配最適化、DP方程式:\( dp [i] [p] = min \ {dp [j] [p-1] +(s [i] −s [j])^ 2 \} \)

おすすめ

転載: www.cnblogs.com/luoyj/p/12714103.html
おすすめ