区间DP QWQ

例题1:石子合并(弱化版)

区间DP合并 模板

题目描述

设有 N ( N ≤ 300 ) N(N \le 300) N(N300) 堆石子排成一排,其编号为 1 , 2 , 3 , ⋯   , N 1,2,3,\cdots,N 1,2,3,,N。每堆石子有一定的质量 m i   ( m i ≤ 1000 ) m_i\ (m_i \le 1000) mi (mi1000)。现在要将这 N N N 堆石子合并成为一堆。每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻。合并时由于选择的顺序不同,合并的总代价也不相同。试找出一种合理的方法,使总的代价最小,并输出最小代价。

输入格式

第一行,一个整数 N N N

第二行, N N N 个整数 m i m_i mi

输出格式

输出文件仅一个整数,也就是最小代价。

样例 #1

样例输入 #1

4
2 5 3 1

样例输出 #1

22

解决思路

对于区间DP来说,有明显的合并特征的题型,一般是在先枚举区间长度 L ,在枚举右端点 i ,通过右端点以及长度来的到左端点 j ,最后枚举中间节点 k 。
当然对于这道题目,是需要求区间的和,可以使用前缀和优化。

状态

设dp[i][j] 表示 合并 区间 [ i , j ] 的 代价。
pre[i] 表示前 i 个数的前缀和

转移

dp[i][j] = max(dp[i][j] , dp[i][k] + dp[k+1][j] + pre[j] - pre[i-1])

代码:

#include <bits/stdc++.h>

using namespace std;
const int N = 305;
int dp[N][N];
int a[N],pre[N];
int n;
int main()
{
    cin >> n;
    for(int i=1;i<=n;i++) cin >> a[i];
    memset(dp,127,sizeof(dp)); // 初始化为最小
    for(int i=1;i<=n;i++) dp[i][i] = 0;
    pre[1] = a[1];
    for(int i=2;i<=n;i++) pre[i] = pre[i-1] + a[i]; // 前缀和优化
    for(int l=2;l<=n;l++){ // 因为以及特殊处理了长度为 1 的各个数 所以长度从 2 开始
        for(int i=1;i+l-1<=n;i++){
            int j = i+l-1;
            for(int k=i;k<=j;k++){ // 枚举中间点 k  
                dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j]+ pre[j]-pre[i-1]);
            }
        }
    }
    cout << dp[1][n];
    return 0; 
}

例题2 [NOI1995] 石子合并

区间DP 断环成链 模板

题目描述

在一个圆形操场的四周摆放 N N N 堆石子,现要将石子有次序地合并成一堆,规定每次只能选相邻的 2 2 2 堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出一个算法,计算出将 N N N 堆石子合并成 1 1 1 堆的最小得分和最大得分。

输入格式

数据的第 1 1 1 行是正整数 N N N,表示有 N N N 堆石子。

2 2 2 行有 N N N 个整数,第 i i i 个整数 a i a_i ai 表示第 i i i 堆石子的个数。

输出格式

输出共 2 2 2 行,第 1 1 1 行为最小得分,第 2 2 2 行为最大得分。

样例 #1

样例输入 #1

4
4 5 9 4

样例输出 #1

43
54

提示

1 ≤ N ≤ 100 1\leq N\leq 100 1N100 0 ≤ a i ≤ 20 0\leq a_i\leq 20 0ai20

解决思路

因为这是一个换,就可以采用 断环成链 的思想解题。具体实现就是将 a 数组存两份

所以不管如何移动区间都可以满足情况。

对于这道题目,可以同时记录最大和最小

注:不能直接输出dp[1][n],因为不知道是保留哪条边来的。最后进行枚举得出答案

代码:

#include <bits/stdc++.h>

using namespace std;
const int N = 300;
int n;
int a[N];
int pre[N];
int dp1[N][N];
int dp2[N][N];
int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
        a[i+n] = a[i]; // 断环成链 存两次
    }
    n<<=1; // 改变 n 的大小
    memset(dp1,127,sizeof(dp1));memset(dp2,128,sizeof(dp2));
    // 初始化 一个最大 一个最小
    for(int i = 1; i <= n; i++){
        pre[i] = pre[i-1] + a[i]; // 仍然进行前缀和优化
        dp1[i][i] = 0;dp2[i][i] = 0; // 合并本身的代价为0
    }
    for(int l = 2; l <= n ; l++){
        for(int i = 1; i+l-1 <=n; i++){
            int j = i+l-1;
            for(int k = i; k < j; k++){
                dp1[i][j] = min(dp1[i][j],dp1[i][k]+dp1[k+1][j]+pre[j]-pre[i-1]);
                dp2[i][j] = max(dp2[i][j],dp2[i][k]+dp2[k+1][j]+pre[j]-pre[i-1]);
            }
        }
    }
    int mx = INT_MIN,mi = INT_MAX;
    n>>=1;
    // 将 n 还原枚举 [1,n]区间最大最小值
    for(int i=1;i<=n;i++){
        mi = min(mi,dp1[i][i+n-1]);
        mx = max(mx,dp2[i][i+n-1]);
    }
    cout << mi << endl << mx;
    return 0;
}
  • 13
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值