郵局加強版:四邊形不等式優化DP
題目描述
一些村莊建在一條筆直的高速公路邊上,我們用一條坐標軸來描述這條公路,每個村莊的坐標都是整數,沒有兩個村莊的坐標相同。兩個村莊的距離定義為坐標之差的絕對值。我們需要在某些村莊建立郵局。使每個村莊使用與它距離最近的郵局,建立郵局的原則是:所有村莊到各自使用的郵局的距離總和最小。數據規模: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> usingnamespace 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"就行了。
在這道題中,我們要註意三點:
- s數組(決策數組)的初始化
- 循環的次序
- 對郵局多於村莊的特判(血淚)
話不多說,代碼上
#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