poj 1160 谈四边形不等式

来源:互联网 发布:金融网络销售总结 编辑:程序博客网 时间:2024/04/29 08:18

转自:http://www.cnblogs.com/staginner/archive/2012/03/12/2391925.html

      我们可以用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 40int 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 0x3f3f3f3fint 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;}
原创粉丝点击