石子合并问题,经典区间DP
一、直线型
问题描述:
有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值(或最大值)。
思路: 设 DP[ i ][ j ] 表示第 i 堆合并到第 j 堆的代价的最小值(或最大值),然后,我们假设,sum[ i ] 为前 i 堆石子的总和,即前缀和。
然后就可以枚举区间长度,枚举中间点, 进行DP了, 复杂度o(n^3)
dp[ i ][ j ] = 0; i = j;
dp[ i ][ j ] = dp[ i ][ k ] + dp[ k + 1 ][ j ] + sum[ j ] - sum[ i - 1 ]; i != j; i <= k < j;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> #define LL long long #define mem(i, j) memset(i, j, sizeof(i)) #define rep(i, j, k) for(LL i = j; i <= k; i++) #define dep(i, j, k) for(int i = k; i >= j; i--) #define pb push_back #define make make_pair #define INF INT_MAX #define inf LLONG_MAX using namespace std; const int N = 1e3 + 5; int dp[N][N], sum[N]; int main() { int n; scanf("%d", &n); rep(i, 1, n) { int x; scanf("%d", &x); sum[i] = sum[i - 1] + x; dp[i][i] = 0; } rep(len, 1, n) { rep(i, 1, n - len) { int j = i + len; dp[i][j] = INF; rep(k, i, j - 1) { dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1]); } } } printf("%d\n", dp[1][n]); }
平行四边形优化
用 p[ i ][ j ] 表示区间 [ i, j ]的最优分割点, 这样, 我们就可将找最优分割点的那个地方,优化一下了。
本来我们需要枚举, [ i, j - 1 ] 现在只需要枚举 [ p[ i ][ j - 1] , p[ i + 1 ][ j ] ] 就行了。
关于这个优化的证明 -> 这个 博客
时间复杂度,接近o(n^2)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <cstdio> #include <string> #include <cstring> #include <algorithm> #define LL long long #define mem(i, j) memset((i), (j), sizeof(i)) #define rep(i, j, k) for(LL i = j; i <= k; i++) #define dep(i, j, k) for(int i = k; i >= j; i--) #define pb push_back #define make make_pair #define INF 0x3f3f3f3f #define inf 0x3f3f3f3f3f3f using namespace std; const int N = 2e3 + 5; int dp[N][N], sum[N], p[N][N], a[N]; int main() { int n; while(~scanf("%d", &n)) { mem(dp, 0x3f); sum[0] = 0; rep(i, 1, n) { scanf("%d", &a[i]); sum[i] = sum[i - 1] + a[i]; dp[i][i] = 0; p[i][i] = i; } rep(len, 2, n) { rep(i, 1, n - 1) { int j = i + len - 1; if(j > n) break; rep(k, p[i][j-1], p[i + 1][j]) { if(dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1] < dp[i][j]) { dp[i][j] = dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1]; p[i][j] = k; } } } } printf("%d\n", dp[1][n]); } return 0; }
还有一个算法可以解决这种问题, GarsiaWachs算法 针对数据大的,开不了二维数组的, 复杂度也是 o(n^2)
二、环型
和上面题型类似, 就是这里的石子堆是围成一个环的,其他条件一致,还是求代价的最小值(或最大)。
思路: 其实也不难, 就把, a[ 1 ] ~ a[ n - 1] 复制一份,存到a[ n + 1 ] ~ a[ n + n - 1 ] 然后, 还是像上面一样做。
最终答案就是, min(dp[ i ][ i + n - 1 ]) 1 <= i <= n;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <cstdio> #include <string> #include <cstring> #include <algorithm> #define LL long long #define mem(i, j) memset((i), (j), sizeof(i)) #define rep(i, j, k) for(LL i = j; i <= k; i++) #define dep(i, j, k) for(int i = k; i >= j; i--) #define pb push_back #define make make_pair #define INF 0x3f3f3f3f #define inf 0x3f3f3f3f3f3f using namespace std; const int N = 2e3 + 5; int dp[N][N], sum[N], p[N][N], a[N]; int main() { int n; while(~scanf("%d", &n)) { mem(dp, 0x3f); sum[0] = 0; rep(i, 1, n) { scanf("%d", &a[i]); sum[i] = sum[i - 1] + a[i]; dp[i][i] = 0; p[i][i] = i; } rep(i, 1, n - 1) { sum[i + n] = sum[i + n - 1] + a[i]; p[i + n][i + n] = i + n; dp[i + n][i + n] = 0; } rep(len, 2, n) { rep(i, 1, (2 * n) - 1) { int j = i + len - 1; if(j > 2 * n - 1) break; rep(k, p[i][j-1], p[i + 1][j]) { if(dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1] < dp[i][j]) { dp[i][j] = dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1]; p[i][j] = k; } } } } int ans = INF; rep(i, 1, n) ans = min(ans, dp[i][i + n - 1]); printf("%d\n", ans); } return 0; }