区间-dp

\(\text{poj-1651}\)

给定长度为 \(n\) 的序列 \(a\),将进行 \(n - 2\) 次操作:

  • 选择一个下标 \(i \in [2, m - 1]\),其中 \(m\) 为当前序列长度。
  • 代价增加 \(a_{i - 1} \cdot a_i \cdot a_{i + 1}\)
  • 从序列中删除 \(a_i\)

求进行完 \(n - 2\) 次操作,代价的最小值。

\(3 \le n \le 100\)


区间 dp 模板题。

\(f_{l,r}\) 表示删除 \(a_{l+1}, \dots, a_{r-1}\) 的最小代价。

初始化如下:

\[f_{l,r} = \left\{ \begin{align} 0 &, r - l < 2 \\ \infty &, r - l \ge 2 \end{align} \right. \]

那么转移方程也很显然了:

\[f_{l,r} = \min_{l < k < r} (f_{l, r}, f_{l, k} + f_{k, r} + a_l \cdot a_k \cdot a_r) \]

最后答案即为 \(f_{1,n}\)

#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
#define MAXN 105
#define INF 0x3f3f3f3f

ll read() {
    ll x = 0, f = 1;
    char c = getchar();
    while(c < 48 || c > 57) { if(c == 45) f = -1; c = getchar(); }
    while(c >= 48 && c <= 57) { x = (x << 3) + (x << 1) + (c - 48); c = getchar(); }
    return x * f;
}

ll n, a[MAXN], dp[MAXN][MAXN];

int main() {
    n = read();
    for(int i = 1; i <= n; i ++) a[i] = read();
    for(int len = 3; len <= n; len ++) 
        for(int l = 1; l + len - 1 <= n; l ++) {
            ll r = l + len - 1; dp[l][r] = INF;
            for(int k = l + 1; k < r; k ++)
                dp[l][r] = min(dp[l][r], dp[l][k] + dp[k][r] + a[l] * a[k] * a[r]);
        }
    cout << dp[1][n] << "\n";
    return 0;
}

\(\text{loj-10147}\)

\(n\) 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。

请编写一个程序,读入堆数 \(n\) 及每堆的石子数,并进行如下计算:

  1. 选择一种合并石子的方案,使得做 \(n-1\) 次合并得分总和最大。
  2. 选择一种合并石子的方案,使得做 \(n-1\) 次合并得分总和最小。

\(1 \le n \le 200\)


区间 dp 模板题。

和 poj-1651 的整体思路差不多。转移方程如下:

\[\begin{align} f_{l,r,0} = \max_{l \le k < r} (f_{l,r,0}, f_{l,k,0} + f_{k+1,r,0} + \sum_{i=l}^r a_i) \\ f_{l,r,1} = \min_{l \le k < r} (f_{l,r,1}, f_{l,k,1} + f_{k+1,r,1} + \sum_{i=l}^r a_i) \end{align} \]

其中,求和可以用前缀和优化。答案为 \(\min\limits_{i=1}^n f_{i,i+n,0}\)\(\max\limits_{i=1}^n f_{i,i+n,1}\)

总时间复杂度为 \(O(n^3)\)

#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
#define MAXN 405
#define INF 0x3f3f3f3f

ll read() {
    ll x = 0, f = 1;
    char c = getchar();
    while(c < 48 || c > 57) { if(c == 45) f = -1; c = getchar(); }
    while(c >= 48 && c <= 57) { x = (x << 3) + (x << 1) + (c - 48); c = getchar(); }
    return x * f;
}

ll n, a[MAXN], mn[MAXN][MAXN], mx[MAXN][MAXN], p[MAXN];

int main() {
    n = read();
    for(int i = 1; i <= n; i ++) a[i + n] = a[i] = read();
    for(int i = 1; i <= 2 * n; i ++) p[i] = p[i - 1] + a[i], mn[i][i] = mx[i][i] = 0;
    for(int len = 2; len <= n; len ++) for(int l = 1; l + len - 1 <= 2 * n; l ++) {
        ll r = l + len - 1; mn[l][r] = INF, mx[l][r] = 0;
        for(int k = l; k < r; k ++)
            mn[l][r] = min(mn[l][r], mn[l][k] + mn[k + 1][r] + p[r] - p[l - 1]), 
            mx[l][r] = max(mx[l][r], mx[l][k] + mx[k + 1][r] + p[r] - p[l - 1]);
    }
    ll maxn = 0, minn = INF;
    for(int i = 1; i <= n; i ++) 
        maxn = max(maxn, mx[i][i + n - 1]), 
        minn = min(minn, mn[i][i + n - 1]);
    cout << minn << "\n" << maxn << "\n";
    return 0;
}

\(\text{loj-10148}\)

原题来自:NOIP 2006

在 Mars 星球上,每个 Mars 人都随身佩带着一串能量项链。在项链上有 \(N\) 颗能量珠。能量珠是一颗有头标记和尾标记的珠子,这些标记对应着某个正整数。并且,对于相邻的两颗珠子,前一颗珠子的尾标记必定等于后一颗珠子的头标记。因为只有这样,通过吸盘——Mars 人吸收能量的器官的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可被吸盘吸收的能量。如果一颗能量珠头标记为 \(m\),尾标记为 \(r\),后一颗能量珠头标记为 \(r\),尾标记为 \(n\),则聚合后释放出 \(m\times r\times n\) Mars单位的能量,新珠子头标记为 \(m\),尾标记为 \(n\)

当需要时,Mars 人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。显然,不同的聚合顺序得到的总能量是不一样的。请设计一个聚合顺序使得一串珠子聚合后释放出的总能量最大。

例如,设 \(N=4\),四颗珠子头标记与尾标记分别为 \((2,3),(3,5),(5,10),(10,2)\)。我们用记号 \(\bigotimes\) 表示两颗珠子的聚合操作,\((j\bigotimes k)\) 表示 \(j,k\) 两颗珠子聚合后释放出的能量,则\(4,1\)两颗珠子聚合后所释放的能量为\((4\bigotimes 1)=10\times 2\times 3=60\),这一串项链可以得到最优值的一个聚合顺序所释放出的总能量为\((((4\bigotimes 1)\bigotimes 2)\bigotimes 3)=\) \(10\times 2\times 3+10\times 3\times 5+10\times 5\times 10=710\)

现在给你一串项链,项链上有 \(n\) 颗珠子,相邻两颗珠子可以合并成一个,合并同时会放出一定的能量,不同珠子合并放出能量不相同,请问按怎样的次序合并才能使得释放的能量最多?

\(4 \le n \le 100\)


与 poj-1651 的唯一区别在于本题为环,因此只需要断环成链即可。

注意,本题要求最大值。

#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
#define MAXN 205

ll read() {
    ll x = 0, f = 1;
    char c = getchar();
    while(c < 48 || c > 57) { if(c == 45) f = -1; c = getchar(); }
    while(c >= 48 && c <= 57) { x = (x << 3) + (x << 1) + (c - 48); c = getchar(); }
    return x * f;
}

ll n, a[MAXN], dp[MAXN][MAXN];

int main() {
    n = read();
    for(int i = 1; i <= n; i ++) a[i + n] = a[i] = read();
    for(int len = 2; len <= n + 1; len ++) for(int l = 1; l + len - 1 <= 2 * n; l ++) {
        ll r = l + len - 1; dp[l][r] = 0;
        for(int k = l + 1; k < r; k ++)
            dp[l][r] = max(dp[l][r], dp[l][k] + dp[k][r] + a[l] * a[k] * a[r]);
    }
    ll res = 0;
    for(int i = 1; i <= n; i ++) res = max(res, dp[i][i + n]);
    cout << res << "\n";
    return 0;
}
posted @ 2026-02-25 21:11  So_noSlack  阅读(6)  评论(0)    收藏  举报