动态规划 —— 区间 DP —— 石子合并三讲

石子合并问题是最经典的 DP 问题,其有如下3种题型:

【任意合并】

1.问题:

有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。

2.分析:

最简单的情况,合并的是任意两堆,直接贪心即可,每次选择最小的两堆合并,实际上就是哈夫曼树的变形。

3.实现:

int a[N];
int Compare(const void *pleft, const void *pright) {
    int *left = (int *)pleft;
    int *right = (int *)pright;
    return (*left - *right);
}
int main() {
    int n;
    while(scanf("%d", &n)!=EOF&&n) {
        for(int i = 0; i < n; i++)
            scanf("%d", &a[i]);

        for(int i = 0; i < n; i++) {
            qsort(&a[i], n - i, sizeof(a[0]), Compare);

            for(int j = 0; j < n; j++)
                printf("%d ", a[j]);
            printf("\n");
            
            a[i+1] += a[i]; //此时a[i]和a[i+1]就是最小的两个
        }

        printf("%d\n", a[n-1]);
    }
}

【直线下的相邻合并】

1.问题:

有N堆石子直线排列,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。

2.分析:

我们熟悉矩阵连乘,知道矩阵连乘也是每次合并相邻的两个矩阵,那么石子合并可以用矩阵连乘的方式来解决。

设 dp[i][j] 表示第 i 到第 j 堆石子合并的最优值,sum[i][j] 表示第 i 到第 j 堆石子的总数量。

那么就有状态转移方程:

3.实现

int dp[N][N];  
int sum[N];  
int a[N];  
int getMinval(int a[],int n)  
{  
    for(int i=0;i<n;i++)  
        dp[i][i] = 0;  
    for(int v=1;v<n;v++)  
    {  
        for(int i=0;i<n-v;i++)  
        {  
            int j = i + v;  
            dp[i][j] = INF;  
            int tmp = sum[j] - (i > 0 ? sum[i-1]:0);  
            for(int k=i;k<j;k++)  
                dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j] + tmp);  
        }  
    }  
    return dp[0][n-1];  
}  
int main()  
{  
    int n;  
    while(scanf("%d",&n)!=EOF)  
    {  
        for(int i=0;i<n;i++)  
            scanf("%d",&a[i]);  
        sum[0] = a[0];  
        for(int i=1;i<n;i++)  
            sum[i] = sum[i-1] + a[i];  
        printf("%d\n",getMinval(a,n));  
    }  
    return 0;  
}  

平行四边形优化

int dp[N][N];  
int p[N][N];  
int sum[N];  
int n;  
int getMinval()  
{  
    for(int i=1; i<=n; i++)  
    {  
        dp[i][i] = 0;  
        p[i][i] = i;  
    }  
    for(int len=1; len<n; len++)  
    {  
        for(int i=1; i+len<=n; i++)  
        {  
            int end = i+len;  
            int tmp = INF;  
            int k = 0;  
            for(int j=p[i][end-1]; j<=p[i+1][end]; j++)  
            {  
                if(dp[i][j] + dp[j+1][end] + sum[end] - sum[i-1] < tmp)  
                {  
                    tmp = dp[i][j] + dp[j+1][end] + sum[end] - sum[i-1];  
                    k = j;  
                }  
            }  
            dp[i][end] = tmp;  
            p[i][end] = k;  
        }  
    }  
    return dp[1][n];  
}  
int main()  
{  
    while(scanf("%d",&n)!=EOF)  
    {  
        sum[0] = 0;  
        for(int i=1; i<=n; i++)  
        {  
            int val;  
            scanf("%d",&val);  
            sum[i] = sum[i-1] + val;  
        }  
        printf("%d\n",getMinval());  
    }  
    return 0;  
}  

【环形下的相邻合并】

1.问题

有N堆石子环形排列,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。

2.分析:

状态转移方程:

其中:

3.实现:

int mins[N][N];  
int maxs[N][N];  
int sum[N],a[N];  
int minval,maxval;  
int n;  
int getsum(int i,int j)  
{  
    if(i+j >= n) 
        return getsum(i,n-i-1) + getsum(0,(i+j)%n);  
    else 
        return sum[i+j] - (i>0 ? sum[i-1]:0);  
}  
void Work(int a[],int n)  
{  
    for(int i=0;i<n;i++)  
        mins[i][0] = maxs[i][0] = 0;  
    for(int j=1;j<n;j++)  
    {  
        for(int i=0;i<n;i++)  
        {  
            mins[i][j] = INF;  
            maxs[i][j] = 0;  
            for(int k=0;k<j;k++)  
            {  
                mins[i][j] = min(mins[i][j],mins[i][k] + mins[(i+k+1)%n][j-k-1] + getsum(i,j));  
                maxs[i][j] = max(maxs[i][j],maxs[i][k] + maxs[(i+k+1)%n][j-k-1] + getsum(i,j));  
            }  
        }  
    }  
    minval = mins[0][n-1];  
    maxval = maxs[0][n-1];  
    for(int i=0;i<n;i++)  
    {  
        minval = min(minval,mins[i][n-1]);  
        maxval = max(maxval,maxs[i][n-1]);  
    }  
}  
  
int main()  
{  
    while(scanf("%d",&n)!=EOF)  
    {  
        for(int i=0;i<n;i++)  
            scanf("%d",&a[i]);  
        sum[0] = a[0];  
        for(int i=1;i<n;i++)  
            sum[i] = sum[i-1] + a[i];  
        Work(a,n);  
        printf("%d %d\n",minval,maxval);  
    }  
    return 0;  
} 

 

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值