HihoCoder - 1636 Pangu and Stones 区间dp

题意:

给定n堆石子,每次可以合并其中相邻的一些堆,合并的数目在区间[L,R]中,每次合并的花费是他们数量的和

思路:

明显的区间dp,但是有个点很巧

因为每次合并都要限定合并的个数,所以区间dp的时候要记录个数状态;

容易想到的是方程是:dp[i][j][k] 表示把 i 到 j 区间合并后,得到 k 堆石子的 最小花费,

唯一的转移的方式就是合并成一堆的时候,

其余的情况只是等式的转移 例如:dp[i][j][k] = min(dp[i][j][k], dp[i][t][x]+dp[t+1][j][k-x])   原本的k堆石子并没有产生价值的转移

 

正常的想法是要枚举 区间两个端点 i,j 两层循环;得到的k堆 一层循环; 区间转移是中间某个值t 一层循环,区间 i-t 得到的x堆石子 一层循环,,一共五重循环,稳稳的炸。。。。。。。。。。

所以这个题的关键所在就是上述的x不需要枚举,把他当做1,因为在枚举 i j 的时候相应的各种情况都能包含到

这时候只需要4层循环,还要单独处理合并成一堆的情况,枚举t,由子区间的满足区间[L,R]的情况合并而来

 

 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100 + 7;
const ll INF = 0x3f3f3f3f;

int n, L, R;
ll a[maxn];
ll sum[maxn];
ll dp[maxn][maxn][maxn];

int main() {
    while(~scanf("%d%d%d", &n, &L, &R) && n) {
        sum[0] = 0;
        for(int i = 1; i <= n; ++i) {
            scanf("%lld", &a[i]);
            sum[i] = sum[i-1] + a[i];
        }
        for(int i = 1; i <= n; ++i) {
            for(int j = i; j <= n; ++j) {
                for(int k = 1; k <= n; ++k) {
                    dp[i][j][k] = INF;
                }
            }
        }
        for(int i = 1; i <= n; ++i) {
            dp[i][i][1] = 0;
            for(int j = i; j <= n; ++j) {
                dp[i][j][j-i+1] = 0;
            }
        }

        for(int len = 2; len <= n; ++len) {
            for(int i = 1; i <= n; ++i) { // [i  ,  i+len-1] qujian
                if(i+len-1 > n) break;
                int j = i+len-1;
                for(int t = i; t < j; ++t) {
                    for(int x = L-1; x < R; ++x) {
                        dp[i][j][1] = min(dp[i][j][1], dp[i][t][1]+dp[t+1][j][x]+(sum[j]-sum[i-1]));
                    }
                }
                for(int k = 2; k <= len; ++k) {
                    for(int t = i; t < j; ++t) {
                        if(j-t<k-1)break;
                        dp[i][j][k] = min(dp[i][j][k], dp[i][t][1]+dp[t+1][j][k-1]);
                    }
                }
            }
        }
//        cout << INF << endl;
        printf("%lld\n", dp[1][n][1] == INF ? 0LL : dp[1][n][1]);
    }

    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值