居然是个环= =…
思路
区间dp,要看怎么合并最小和最大,我们自然就想到枚举所有可能的区间!难点就是状态转移方程啦(☞完全不会)。
状态转移方程: dp[i][j] = dp[i][k] + dp[k + 1][j] + sum[i][j] (i <= k < j)
dp[i][j] -> i 到 j 的区间合并,sum[i][j] -> i 到 j 堆的值的和
直接举例,按照样例4 5 9 4这样的四颗石子来!
刚开始的时候每个dp区间都是自己到自己,初始化为 0dp[i][i] = 0。
1、 [1] [2]两堆合并dp[1][2] = dp[1][1] + dp[2][2] + sum[1][2],即[1]自己的区间加上[2]自己的区间再加上 1~2 区间的石头的值的和,我们就得出了dp[1][2] = 9。
2、 [1] [2] [3]三堆合并,此时存在两种情况:
dp[1][3](情况1) = dp[1][2] + dp[3][3] + sum[1][3]
在这种情况下我们是用[1] [2]先进行合并,而后再合成[3]。
dp[1][3](情况2) = dp[1][1] + dp[2][3] + sum[1][3]
在这种情况下我们是用[2] [3]先进行合并,而后再合成[1]。
同理求得:
dp[2][3] = dp[2][2] + dp[3][3] + sum[2][3] = 14
经过计算:
dp[1][3](情况1) = 9 + 18 = 27
dp[1][3](情况2) = 14 + 18 = 32
这时候其实我们已经可以一下求出两种结果了。
此时的max = 32, min = 27。
3、重复上述步骤继续合并!直到找区间 i ~ j ( i + j - 1 = n )最大和最小值!
要实现上述的计算要怎样进行枚举呢?
1、 最外面一层 len 从 2 到 n 表示区间[i, j]的长度
2、 第二层枚举起点 i 的位置,i 枚举 从 1 开始到 n - len 结束
3、 因为有了起点 i 和区间的长度len,所以我们的 j 其实就已经可以得出了 j = i + len。
4、 在len长的区间[i , j]枚举每个分割可能的位置 k ,使得 dp[i, j] = dp[i , k] + dp[k + 1, j];所以我们的 k 从 i 开始枚举,到 j 结束( i <= k < j )
5、 需要注意的两点是,First. 由于 i 从 1 开始, j = i + len = 2,此时区间的长度就已经是 2 了,而枚举len = 3时, i(max) = n - 3 = 1时(样例n = 4),此时 j = len + i = 4,所以我们 len 的结尾是 < n 而不是 = n ,因此我们枚举 len 的时候实际是从 1 <= len < n。
Second. 枚举 k 的时候我们从 k = i 开始枚举的,但我们枚举不能到 k = j 的时候。
dp[i, j] = dp[i , k] + dp[k + 1, j];
由这个式子可以看出, dp[k + 1, j]中如果 k = j , 那么k + 1就会大于 j 了!!!所以这就有问题了!因此( i <= k < j )。
6、sum[i][j]我们用来表示区间 i ~ j 的和,而在实际的使用中,我们可以用前缀和来使其简化成一维,方便计算!
我们还是看到样例:
4 5 9 4
sum[0]= 0
sum[1]= 4
sum[2] = sum[1] + 5 = 9
sum[3] = sum[2] + 9 = 18
sum[4] = sum[3] + 4 = 22
我们如果要求sum[1][3]的话我们只要用sum[3] - sum[1 - 1] = sum[3] - sum[0] = 18 就可以得到我们要的结果啦!
由此我们可以得出sum[i][j] = sum[j] - sum[i - 1]
7、这是最要命的一点!!!!!真的超毒的!!!这是个环!环!环!也就是说4 5 9 4可以变成5 9 4 4,9 4 4 5,4 4 5 9一共由四种情况!!!那这样要怎么处理呢?
其实也简单,环转链即可,n 变成 2n 把他复制一遍拉长,变成
4 5 9 4 4 5 9 4
这样,我们就区间[i ,j]就能包括所有情况了!,还有求最小值的时候记得把dp[i][j]初始化为一个很大很大的值,不然得出结果就全都是 0 了!
可以用下面这个统一初始化的方法
#include <cstring>
//初始化dp里每个字节为 0x3f,dp[i][j] = 0x3f3f3f3f = 1061109567
memset(dp, 0x3f, sizeof(dp));
//初始化dp里每个字节为0
memset(dp, 0, sizeof(dp));
以上!题解完成!上代码!
#include <iostream>
using namespace std;
const int N = 300;
//相当于1 * 2^30, << 是左移位运算,每次都移动一位都相当于×2
const int INF = 1 << 30;
int n, dp[N][N], dp2[N][N];
int sum[N], s[N];
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> s[i];
//为n~2n的前缀和做准备
s[i + n] = s[i];
}
// n * 2 是因为要化环为链
for(int i = 1; i <= n * 2; i++)
{
//求前缀和,sum[i, j] -> sum[i][i - j + 1] -> sum[j] - sum[i - 1];
sum[i] = s[i] + sum[i - 1];
//虽然已经全局初始化为0了但是题解还是写下吧
dp[i][i] = 0; dp2[i][i] = 0;
}
//len:i 到 j 的距离
for(int len = 1; len < n; len++)
{
//从第 i 堆开始
for (int i = 1; i <= (n * 2 - len); i++)
{
//到第 j 堆结束
int j = i + len;
//初始化求最小的区间和为 INF(因为一开始是 0 ,不放大就一直是 0 了)
dp[i][j] = INF;
// i 和 j 之间用 k 分割
for(int k = i; k < j; k++)
{
//第 i 堆到第 j 堆的合并
dp[i][j] = min (dp[i][j], dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1]);
dp2[i][j] = max (dp2[i][j], dp2[i][k] + dp2[k + 1][j] + sum[j] - sum[i - 1]);
}
}
}
//看化为链的环中哪点为起点时有最大值或最小值
int ans = INF, ans2 = 0;
for(int i = 1; i <= n; i++)
{
//i为起点 j = i + n - 1 ,len = i + j = n
ans = min(ans, dp[i][i + n - 1]);
ans2 = max(ans2,dp2[i][i + n - 1]);
}
cout << ans << endl << ans2;
}
这篇博客探讨了一种区间动态规划的解决方案,涉及如何合并最小和最大堆的策略。通过枚举所有可能的区间,利用状态转移方程进行计算,并处理环形数据结构。博主介绍了将环形结构转化为链状结构的方法,以覆盖所有情况,并提供了C++代码示例。文章强调了初始化、前缀和以及区间和的计算技巧。
278

被折叠的 条评论
为什么被折叠?



