石子合并——圆形版

石子合并——圆形版

一个较为常规的dp,用上了环的处理。

问题描述

Submit Status
    虽然方老师赚到了很多钱,但是有些钱却非常难处理,经常会有人会以一大袋金币的方式来支付方老师的演讲报酬。现在方老师家里已经放置了N袋金币了,每一袋金币质量为Ai,他想把这些金币合并在一袋里面,然后存到银行去,于是方老师把这N袋金币围成一个圈,每一次他可以把相邻两袋金币合并,并消耗两袋金币质量总和的体力,他想让你帮他算下,他最少消耗多少体力可以完成这项工作。

注意:开始时第i袋金币与第i+1袋金币相邻,第1袋金币与第N袋金币相邻。

dp思路

    将问题简化到直线上,观察每次合并的过程,发现对于区间[i, j],无论中间过程是怎样的,最后一次合并所花费的体力都是一定的,大小为区间[i, j]内的金币的总重量,且对于区间[i, j],当分成的两部分的体力花费最小时,总花费最小。
    故易得出状态转移方程
    d[i][j] = min(d[i][k]+d[k+1][j], d[i][j]) + sum[j] - sum[i]; (i<=k<j)
    然后,考虑环,根据以往处理环的经验,(i+k)%n可得出i后第k个数字,但是此处的j不方便用这个方法表示,故将d[i][j]的定义改为第i个数字及后j个数字的最小解。此时可得新的转移方程
    d[i][j] = min(d[i][k] + d[(i+k)%n+1][j-k-1], d[i][j]) + sum[i][j]; (0<=k<j)

代码

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
using namespace std;

const int INF = 2139062143;
int n;
int sum[1005][1005], a[1005];
int d[1005][1005];  

void read_in() {
    cin >> n;
    a[0] = 0;
    for(int i = 1; i <= n; i++){
        int x;
        scanf("%d", &x);
        a[i] = a[i-1] + x;
    }
        for(int i = 1; i <= n; i++)
            for(int j = 1; j < n; j++)
                if((i+j) > n) sum[i][j] = a[n] - a[i-1] + a[(i+j)%n];
                else sum[i][j] = a[i+j] - a[i-1];
    return;
}

int dp(int i, int j){
    if(d[i][j] < INF) return d[i][j];
    int m = INF;
    for(int k = 0; k < j; k++)
        m = min(dp(i, k) + dp((i+k)%n+1,j-k-1), m);
    return d[i][j] = m + sum[i][j];
}

int main() {
    read_in();
    memset(d, 127, sizeof(d));
    for(int i = 1; i <= n; i++)
        d[i][0] = 0;    
    int min_ans = INF;
    for(int i = 1; i <= n; i++) min_ans = min(min_ans, dp(i, n-1));
    cout << min_ans;
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值