1. 程式人生 > >poj 1160 談四邊形不等式

poj 1160 談四邊形不等式

我們可以用f[i][j]表示建好i個郵局時覆蓋到第j個村莊的最優解,那麼就可以得到f[i][j]=min{f[i-1][k]+w[k+1][j]}(k<j),其中w[x][y]表示建一個郵局覆蓋x到y的村莊的距離和,w[x][y]可以事先預處理出來。

    這個題目還可以用四邊形不等式去優化,實際上四邊形不等式優化難點不在於應用,只是在K[i][j-1]<=k<=K[i+1][j]中去選擇更新f[i][j]的k即可,比較複雜的部分就在於對k可以這樣選擇做出證明。

    一般四邊形不等式的證明步驟如下:

    ①證明w為凸,這一步用黑書上的定理w為凸當且僅當w[i][j]+w[i+1][j+1]<=w[i][j+1]+w[i+1][j],這樣只要說明w[i+1][j]-w[i][j]是關於j單調遞減的即可。

    ②證明f為凸,這一步要利用①中的定理去證f[i][j]+f[i+1][j+1]<=f[i][j+1]+f[i+1][j],而證明的方法通常是利用w為凸的結論,先假設f[i][j+1]取得最優解是k為x,f[i+1][j]取得最優解時f[i+1][j]為y,然後分x<y和y<x兩種情況,將f[i][j]和f[i+1][j+1]各按k=x或k=y拆開之後,將拆出的w應用四邊形不等式,再將各項合併成f[i][j+1]+f[i+1][j]從而完成證明。

    ③證明K[i][j-1]<=K[i][j]<=K[i+1][j],證明K[i][j-1]<=K[i][j]時,要先假設f[i][j-1]取得最優解時k=y,然後利用x<=y<=j-1<j列一個四邊形不等式,然後在不等式兩邊新增一定的項試圖得到f[i][j-1](k=x)+f[i][j](k=y)<=f[i][j-1](k=y)+f[i][j](k=x),也就是f[i][j-1](k=x)-f[i][j-1](k=y)<= f[i][j](k=x)-f[i][j](k=y),這時我們就會發現因為f[i][j-1](k=y)<=f[i][j-1](k=x),那麼一定有f[i][j](k=y)<=f[i][j](k=x),也就是說對於所有小於y的x,都會有f[i][j-1](k=y)<=f[i][j-1](k=x),那麼也都會有f[i][j](k=y)<=f[i][j](k=x),因此令f[i][j]取得最優解的k一定不小於y,這樣就完成了對K[i][j-1]<=K[i][j]的證明。對於K[i][j]<=K[i+1][j]的證明是類似的。

    其實對於K[i][j-1]<=K[i][j]<=K[i+1][j],我們還可以得到另一個形式的對K[i][j]的約束。由K[i][j-1]<=K[i][j]可以得到K[i][j]<=K[i][j+1],由K[i][j]<=K[i+1][j]可以得到K[i-1][j]<=K[i][j],於是另一個形式就是K[i-1][j]<=K[i][j]<=K[i][j+1]。這個形式顯然對最優二分檢索樹問題是沒有意義的,因為那個題目是按區間的長度由小到大進行dp的,因此在得到K[i][j]之前是不可能得到K[i-1][j]和K[i][j+1]的,而這個題就不同了。

    接下來,不妨分析一下網上見得比較多的兩個四邊形不等式優化的版本的時間複雜度。

    首先,這個題可以用K[i][j-1]<=K[i][j]<=K[i+1][j]去約束k,但這樣就必須按區間[i,j]的長度由小到大進行dp,這樣複雜度是O((V-P)*V),但倘若我們用K[i-1][j]<=K[i][j]<=K[i][j+1]優化,只需要將j逆向迴圈,同時把K[i][V+1]初始化成一個邊界值V即可,但是這樣就是O(P*V)的複雜度嗎?這個是不能說二分檢索樹問題由O(N^3)應用四邊形不等式優化到了O(N^2),那這個問題就能由O(P*V^2)優化成O(P*V)的,因為用的約束k的不等式是不一樣的,因此時間複雜度我們也要實際去證明一下。

    對於更新f[i][j],k是從K[i-1][j]迴圈到K[i][j+1],於是有多少種不同的i、j組合就可以得到多少種K[i-1][j]到K[i][j+1]的區間段,注意到j-(i-1)和j+1-i是相等的,於是我們按區間段的跨度p=j-i+1把K[i-1][j]—K[i][j+1]分成若干組,對於每一組,都可以得到類似…K[i-1][j]—K[i][j+1],K[i][j+1]—K[i+1][j+2],…這樣的排列,我們不妨計算一下在每一組的這些區間上k一共迴圈了多少次。首先,最右邊是K[x][V+1](x是含V、i、j的表示式)形式的,這個值被初始化成了V,而最左邊就應該是K[1][p+1](p表示區間跨度),而K[1][p+1]是被初始化成0的。同時對於每一個K[i][j]都是有具體值的,因此在每一組的這些區間上k迴圈的次數最少的情況就是K[i-1][j]<=K[i][j+1]恆成立的時候,這時迴圈次數是O(V)的,而一共以多少組呢?我們是按區間跨度分的組,區間跨度最大是O(V),所以整體複雜度就是O(V^2)的。

    到這裡,網上看到的兩種解法的時間複雜度就都分析完了,實際上兩種解法都沒到達O(P*V)的複雜度,最好也只是O((V-P)*V)的複雜度。當然,這時也許你該懷疑那種較好的演算法是否真的是O((V-P)*V)的複雜度了,因為我沒有給出實際的證明,這是因為這個時間複雜度的證明就和最優二叉搜尋樹那個題對時間複雜度的證明是一樣的了。

    此外,如果想達到O((V-P)*V)的複雜度,就不能用O(V^2)的形式去預處理w了,而要改成O(V)的形式預處理出字首和,然後O(1)的時間計算出需要的w。其實對於O((V-P)*V)的演算法,因為題目中最壞情況P約為1,這時演算法也就是O(V^2)的了。

複製程式碼
//O(P*V^2)
#include<stdio.h>
#include<string.h>
#define MAXD 310
#define MAXP 40
int N, P, f[MAXP][MAXD], w[MAXD][MAXD], a[MAXD];
void init()
{
    int i, j, k;
    for(i = 1; i <= N; i ++)
    {
        scanf("%d", &a[i]);
        w[i][i] = 0;
    }
    for(k = 1; k < N; k ++)
        for(i = 1; (j = i + k) <= N; i ++)
            w[i][j] = w[i][j - 1] + a[j] - a[(i + j) / 2];
}
void solve()
{
    int i, j, k;
    memset(f, 0x3f, sizeof(f));
    f[0][0] = 0;
    for(i = 1; i <= P; i ++)
        for(j = i; j <= N; j ++)
            for(k = i - 1; k < j; k ++)
                if(f[i - 1][k] + w[k + 1][j] < f[i][j])
                    f[i][j] = f[i - 1][k] + w[k + 1][j];
    printf("%d\n", f[P][N]);
}
int main()
{
    while(scanf("%d%d", &N, &P) == 2)
    {
        init();
        solve();
    }
    return 0;
}
複製程式碼 複製程式碼
//四邊形不等式優化dp
#include<stdio.h>
#include<string.h>
#define MAXD 310
#define MAXP 40
#define INF 0x3f3f3f3f
int N, P, f[MAXD][MAXD], A[MAXD], a[MAXD], K[MAXD][MAXD];
void init()
{
    int i, j, k;
    A[0] = 0;
    for(i = 1; i <= N; i ++)
    {
        scanf("%d", &a[i]);
        A[i] = A[i - 1] + a[i];
    }
}
int getw(int x, int y)
{
    int t = (x + y) / 2;
    return A[y] - A[t] - (y - t) * a[t] + (t - x) * a[t] - (A[t - 1] - A[x - 1]);
}
void solve()
{
    int i, j, k, p, t;
    for(i = 0; i <= N; i ++)
    {
        f[i][i] = 0;
        K[i][i] = i;
    }
    for(p = 1; p <= N - P; p ++)
    {
        for(i = 0; (j = i + p) <= N; i ++)
            f[i][j] = INF;
        for(i = 1; (j = i + p) <= N; i ++)
        {
            for(k = K[i][j - 1]; k <= K[i + 1][j]; k ++)
                if((t = f[i - 1][k - 1] + getw(k, j)) < f[i][j])
                {
                    f[i][j] = t;
                    K[i][j] = k;
                }
        }
    }
    printf("%d\n", f[P][N]);
}
int main()
{
    while(scanf("%d%d", &N, &P) == 2)
    {
        init();
        solve();
    }
    return 0;
}