1. 程式人生 > >用一題來說明dp平行四邊優化

用一題來說明dp平行四邊優化

poj 1160 

題意:給出n個村莊,村莊是一條直線排好的,並且給出每個村莊到直線最左邊初始點的距離。現在要建立m個郵局,於是引入一個距離S{各個村莊到最近的郵局的距離和}。那麼現在問題來了如何使得這個S的值最小?

題解:定義這樣的方程 dp[i][j] = min{ dp[i-1][k] + w[k+1][j] } 我們可以看出這個方程滿足這樣的模型 s[i][j] = s[i][k] + s[k+1][j] 那麼我們可以斷定用平行四邊形優化。那麼定義mark[i][j]表示最大的k就可以把複雜度降到O(n^2)

注意一點,在迴圈的第二層是倒著推的,為什麼?很顯然如果正著推的話會產生後效性,因為我們加入了mark[i][j]這個輔助的原因,如果沒有這個輔助可以正的推,加了之後因為mark[i][n+1]=n,如果正著推發現mark[i][j]都沒計算出來,顯然dp過程就錯了,倒著推很明顯mark[i][n+1]是計算出來的,這樣遞推下去才不會有沒計算的。

#include<iostream>
#include<math.h>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<vector>
#include<map>
using namespace std;
//typedef __int64 lld;
#define oo 0x3f3f3f3f
#define maxn 305
#define maxm 35
int dp[maxn][maxm];
int mark[maxn][maxm];
int w[maxn][maxn],val[maxn];

int main()
{
    int n,m;
    while(scanf("%d %d",&n,&m)!=EOF)
    {
        for(int i=1;i<=n;i++)
            scanf("%d",&val[i]);
        for(int i=1;i<=n;i++)
        {
            for(int j=i+1;j<=n;j++)
                w[i][j]=w[i][j-1]+val[j]-val[(i+j)/2];
        }
        memset(dp,0x3f,sizeof dp);
        for(int i=1;i<=n;i++)
        {
            dp[1][i]=w[1][i];
            mark[1][i]=0;
        }
        for(int i=2;i<=m;i++)
        {
            mark[i][n+1]=n;//倒著推,防止後效性
            for(int j=n;j>=i;j--)
                for(int k=mark[i-1][j];k<=mark[i][j+1];k++)
                    if(dp[i][j]>dp[i-1][k]+w[k+1][j])
                    {
                        dp[i][j]=dp[i-1][k]+w[k+1][j];
                        mark[i][j]=k;
                    }
        }
        printf("%d\n",dp[m][n]);
    }
    return 0;
}