1. 程式人生 > >郵局加強版:四邊形不等式優化DP

郵局加強版:四邊形不等式優化DP

一個 ica 成了 除了 i+1 -a 都是 bit oid

題目描述

一些村莊建在一條筆直的高速公路邊上,我們用一條坐標軸來描述這條公路,每個村莊的坐標都是整數,沒有兩個村莊的坐標相同。兩個村莊的距離定義為坐標之差的絕對值。我們需要在某些村莊建立郵局。使每個村莊使用與它距離最近的郵局,建立郵局的原則是:所有村莊到各自使用的郵局的距離總和最小。數據規模:1<=村莊數<=1600, 1<=郵局數<=200, 1<=村莊坐標<=maxlongint

輸入

2第一行:n m {表示有n個村莊,建立m個郵局} 第二行:a1 a2 a3 .. an {表示n個村莊的坐標}

輸出

1第一行:l 個整數{表示最小距離總和}

樣例輸入

10 5

1 2 3 6 7 9 11 22 44 50

樣例輸出

9

這道題目是IOI2000的真題哦~

可以這樣考慮:

給定一個區間,假設我們要建一個郵局,那麽一定是在這個序列的中點,所以我們可以先預處理出序列區間[i,j]之間的距離

一個郵局的最短距離記錄為sum[i][j],然後用f[i][j]表示到i個村莊建立j個郵局的最短距離和,那麽就有狀態轉移方程:

f[i][j]=min(f[i][j],f[k][j-1]+sum[k+1][i]);

這樣,代碼就好寫了。

但是——這個數據,用O(n3) 的普通DP算法顯然無法通過。

O(n3)代碼如下:

#include<bits/stdc++.h>

 

using
namespace std; int n,m; int a[1666]; long long sum[1666]1666]; long long f[1666][222]; //f[i][j]表示前i個村莊設j個郵局 //sum[i][j]表示在第i個村莊到第j個村莊設一個郵局的路程 int main(void){ cin>>n>>m; for (int i=1;i<=n;i++) cin>>a[i]; sort(a+1,a+n+1); for (int i=1;i<=n;i++){
for (int j=i;j<=n;j++){ sum[i][j]=dis(i,j); } } memset(f,0x3f,sizeof(f)); for (int i=1;i<=n;i++){ f[i][1]=sum[1][i]; } for (int i=1;i<=n;i++){ for (int j=2;j<=min(i,m);j++){ for (int k=j-1;k<=i-1;k++){ f[i][j]=min(f[i][j],f[k][j-1]+sum[k+1][i]); } } } cout<<f[n][m]<<endl; }

這東西肯定過不了啊~

那怎麽辦?"四邊形不等式!"

f[a][c]+f[b][d]<=f[b][c]+f[a][d]

( a < b <= c< d )

(可以理解成:交叉小於包含)

滿足這個條件的DP方程(或者說是別的什麽數組啊之類的)就稱為***為凸。

(以下一段文字來自https://blog.csdn.net/noiau/article/details/72514812)

給出兩個定理:

1、如果上述的w函數同時滿足區間包含單調性和四邊形不等式性質,那麽函數dp也滿足四邊形不等式性質

我們再定義s(i,j)表示 dp(i,j) 取得最優值時對應的下標(即 i≤k≤j 時,k 處的 dp 值最大,則 s(i,j)=k此時有如下定理

2、假如dp(i,j)滿足四邊形不等式,那麽s(i,j)單調,即 s(i,j)≤s(i,j+1)≤s(i+1,j+1)

大家可以自己嘗試推倒一下,為什麽f[i][j]和sum[i][j]是滿足這個式子的(因為我懶得再推了)

再就是要證明"決策單調"

(以下一段文字來自https://blog.csdn.net/noiau/article/details/72514812)

如果我們用s[i][j]表示dp[i][j]取得最優解的時候k的位置的話
那麽我們要證明如下結論的成立性:
s[i][j-1]<=s[i][j]<=s[i+1][j]
對於s[i][j-1]<=s[i][j]來說,我們先令dp[i][j-1]取得最優解的時候的k值為y,然後令除了最優值以外的其他值可以為x,這裏我們由於要討論單調性,所以讓x小於y,即x<=y<=j-1< j

這裏的證明更為繁瑣,在實際應用中,我們可以寫出O(n3)後,自己跑一邊是否決策單調,不是就輸出"false"就行了。

在這道題中,我們要註意三點:

  1. s數組(決策數組)的初始化
  2. 循環的次序
  3. 對郵局多於村莊的特判(血淚)

話不多說,代碼上

#include<bits/stdc++.h>

 

using namespace std;

 

int n,m;

int a[1606];

long long sum[1606][1606];

long long f[1606][202];

int s[1606][202];

//s是決策數組

 

int main(void){

    cin>>n>>m;

    if(m>=n){

printf("0");

return 0;

}

    for (int i=1;i<=n;i++) cin>>a[i];

    sort(a+1,a+n+1);

    for (int i=1;i<=n;i++){

        sum[i][i]=0;

        for (int j=i+1;j<=n;j++){

            sum[i][j]=sum[i][j-1]+a[j]-a[(i+j)/2];

        }

    }

    memset(f,0x3f,sizeof(f));

//註意這裏f要初始化成最大值

    memset(s,0,sizeof(s));

    for (int i=1;i<=n;i++){

        f[i][1]=sum[1][i];

        s[i][1]=0;

    }

    for (int j=2;j<=m;j++){

        s[n+1][j]=n;

        for (int i=n;i>=j;i--){

            for (int k=s[i][j-1];k<=s[i+1][j];k++){

                if (f[k][j-1]+sum[k+1][i]<f[i][j]){

                    f[i][j]=f[k][j-1]+sum[k+1][i];

                    s[i][j]=k;

                }

            }

        }

    }

    cout<<f[n][m]<<endl;

}

這樣的代碼,經過四邊形不等式的優化,就是O(n2)的算法了!

(以下一段文字來自https://blog.csdn.net/noiau/article/details/72514812)

關於O(n^2)復雜度的證明

其實證明很簡單,對於一個i,j來說,我們要for s[i][j-1]到s[i+1][j]個數,那麽所有的i和j加起來一共會for多少次呢?

我們可以這樣思考

(s[2][2]-[1][1])+(s[3][3]-s[2][2])+(s[4][4]-s[3][3])+…+(s[n][n]-s[n-1][n-1])=s[n][n]-s[1][1]很顯然是小於n的嘛,所以本來是(n *n *n)的復雜度,就這樣降成了O(n *n)啦

(關於四邊形不等式強推https://blog.csdn.net/noiau/article/details/72514812這篇博客)

FFFeiya編輯於2018.7.30

郵局加強版:四邊形不等式優化DP