POJ 1160 区间DP + 平行四边形优化

来源:互联网 发布:2017年双十一实时数据 编辑:程序博客网 时间:2024/05/18 21:06

区间DP + 平行四边形优化

题意:

​ 有n个村庄现在要建立m个邮局,问怎么建邮局才能使得村庄到最近的邮局距离和最小。输出距离和即可。

思路:

​ 一般的区间dp是从小区间到大区间,而此题在此之外还有一个限制是m个邮局。对于此类问题可以直接建立dp的时候加上限制条件:dp[i][j]=min(dp[k][j1]+one[k+1][i]) 定义dp含义为前i个村庄建立了j个邮局的最小距离和,那么在建立第j的时候可以枚举之前已经求出的区间,从j-1个邮局的前提下加上现在的1个邮局,one[i][j] 的含义是在区间i到j 的范围之中建立一个邮局,其实也就是中位数的位置,然后递推找出最小的值即可。

  • 未优化代码。
#include <iostream>#include <cstdio>#include <cstring>using namespace std;const int maxn = 305;const int INF = 0x3f3f3f3f;int n,m;int a[maxn];int one[maxn][maxn];int dp[maxn][maxn];int main(){    //freopen("in.txt","r",stdin);    scanf("%d%d",&n,&m);    for(int i = 1;i <= n; i++) {        scanf("%d",&a[i]);    }    for(int i = 1;i <= n; i++) {        for(int j = i;j <= n; j++) {            if(i == j) one[i][j] = 0;            else one[i][j] = one[i][j-1] + a[j] - a[(i+j)/2];        }    }    memset(dp,INF,sizeof(dp));    for(int i = 1;i <= n; i++) dp[i][1] = one[1][i];    for(int i = 2;i <= n; i++) {        int End = min(m,i);        for(int j = 2;j <= End; j++) {            for(int k = 1;k <= i; k++) {                if(dp[i][j] > dp[k][j-1] + one[k+1][i])                    dp[i][j] = dp[k][j-1] + one[k+1][i];            }        }    }    printf("%d\n",dp[n][m]);    return 0;}
  • 可以用四边形优化

平行四边形的ss数组是优化的核心,但是它是建立在:m[i,j]=minm[i,k]+m[k,j](ikj) 条件下的。

ss[i,j1]ss[i,j]ss[i+1,j] 是最优解的优化,但是不知道ss[i+1][j] 如何算出,但是可以知道原本的上界i+1。

#include <iostream>#include <cstdio>#include <cstring>using namespace std;const int maxn = 305;const int INF = 0x3f3f3f3f;int n,m;int a[maxn];int one[maxn][maxn];int dp[maxn][maxn];int ss[maxn][maxn];int main(){    //freopen("in.txt","r",stdin);    scanf("%d%d",&n,&m);    for(int i = 1;i <= n; i++) {        scanf("%d",&a[i]);    }    for(int i = 1;i <= n; i++) {        for(int j = i;j <= n; j++) {            if(i == j) one[i][j] = 0;            else one[i][j] = one[i][j-1] + a[j] - a[(i+j)/2];        }    }    memset(dp,INF,sizeof(dp));    memset(ss,0,sizeof(ss));    for(int i = 1;i <= n; i++) dp[i][1] = one[1][i];    for(int i = 2;i <= n; i++) {        int End = min(m,i);        for(int j = 2;j <= End; j++) {            ss[i+1][j] = i+1;            for(int k = ss[i][j-1];k <= ss[i+1][j]; k++) {                if(dp[i][j] > dp[k][j-1] + one[k+1][i]) {                    dp[i][j] = dp[k][j-1] + one[k+1][i];                    ss[i][j] = k;                }            }        }    }    printf("%d\n",dp[n][m]);    return 0;}
  • 还有一个版本是i和j的循环可以换一换
#include <iostream>#include <cstdio>#include <cstring>using namespace std;#define inf 0x3f3f3f3fint dp[305][305];int x[305],one[305][305]={0};int K[305][305];int main(){    int N,M,i,j,k;   // freopen("in.txt","r",stdin);    while(cin>>N>>M){        for(i=1;i<=N;++i)  scanf("%d",&x[i]);        memset(dp,inf,sizeof(dp));        for(i=1;i<=N;++i)            for(j=i;j<=N;++j)        if(i==j) one[i][i]=0;        else            one[i][j]=one[i][j-1]+x[j]-x[(i+j)/2];        for(i=1;i<=N;++i) dp[i][1]=one[1][i];        for(i=1;i<=N;++i) K[i][i]=1;        for(j=2;j<=M;++j)        {            for(i=1;i<=N;++i)            {                K[i+1][j]=i+1;                if(j>i) continue;                for(k=K[i][j-1];k<=K[i+1][j];++k)                {                   int s=dp[k][j-1]+one[k+1][i];                   if(dp[i][j]>s) {                        dp[i][j]=s;                        K[i][j]=k;                   }                }            }        }        printf("%d\n",dp[N][M]);    }    return 0;}