1. 程式人生 > >[POJ1160] [IOI2000] Post Office [區間dp]

[POJ1160] [IOI2000] Post Office [區間dp]

[ L i n k \frak{Link} ]


有一些村莊,要在裡面選幾個建郵局,求每個村莊到最近的郵局的距離和最小可能是多少。

顯然可以讓f(i,j)表示前i個郵局,前j個村莊。
w(i,j)表示一個郵局覆蓋[i,j]的最小代價,很容易列出方程
f(i,j)=min{f(i-1,k)+w(k+1,j)}
典型的順序遞推區間問題轉移方程。
同時也是典型的可能可以用四邊形不等式/斜率優化的方程。
總之先打個Θ(n3)刷決策單調錶。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<ctime> using std::cin; using std::cout; using std::endl; int nNumVillages; int nNumPostOffices; int nPosVillages[305]; int nLeastSum[305][305]; int nDisSum[305][305]; int nPoint[305][305]; inline void init() { // std::ios::sync_with_stdio(false); // cin.tie(0); // cout.tie(0); } void input()
{ cin >> nNumVillages >> nNumPostOffices; for (register int i = 1; i <= nNumVillages; ++i) { cin >> nPosVillages[i]; } } void work() { for (register int i = 1; i <= nNumVillages; ++i) { for (register int j = i + 1; j <= nNumVillages; ++j) { nDisSum[i][j] = nDisSum[i][j-1] + nPosVillages[j] - nPosVillages[i+j>>1]; } } memset(nLeastSum, 0x3f, sizeof(nLeastSum)); nLeastSum[0][0] = 0; for (register int i = 1; i <= nNumPostOffices; ++i) { for (register int j = 1; j <= nNumVillages; ++j) { for (register int k = 0; k < j; ++k) { if (nLeastSum[i-1][k] + nDisSum[k+1][j] < nLeastSum[i][j]) { nLeastSum[i][j] = nLeastSum[i-1][k] + nDisSum[k+1][j]; nPoint[i][j] = k; } } printf("%3d",nPoint[i][j]); } putchar('\n'); } } inline void output() { cout << nLeastSum[nNumPostOffices][nNumVillages]; } int main() { init(); input(); work(); output(); return 0; }

這道題就A了,還跑得挺快的
對樣例打出來表長這樣:
0 0 0 0 0 0 0 0 0 0
0 1 1 3 3 3 3 7 8 8
0 0 2 3 3 5 5 7 8 8
0 0 0 3 3 5 6 7 8 8
0 0 0 0 4 5 6 7 8 9
再多打幾個表就能發現決策單調。
優化有點麻煩,上面的迴圈是不能直接逆序的,得改一維為列舉區間長
優化後把w用字首和差分求,複雜度約Θ((V-P)V)
code
就不打了(