經典問題二.【區間dp】石子歸併 51nod 1021
阿新 • • 發佈:2019-01-23
51nod 1021 石子歸併(區間dp)
問題描述:
N堆石子擺成一條線。現要將石子有次序地合併成一堆。規定每次只能選相鄰的2堆石子合併成新的一堆,並將新的一堆石子數記為該次合併的代價。計算將N堆石子合併成一堆的最小代價。
括號裡面為總代價可以看出,第一種方法的代價最低,現在給出n堆石子的數量,計算最小合併代價。
思路:
區間dp通過區間的關係操作,從小區間到大區間。區間dp感覺有點像分治一樣的,我們知道如果有兩個數的話,那麼其值就為兩數相加,如果有大於兩個的數,那麼通過區間的合併的最優解能得到答案。
dp表示含義,與遞推式,已程式碼註釋。
AC程式碼
版本一:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
int dp[210][210]; //代表 區間i到j合併成一堆最小代價。
int p[210][210]; //代表 區間i到j的和
int a[210];
int n;
void init()
{
for(int i = 1; i <= n; i++)
{
int sum = 0;
for(int j = i; j <= n; j++)
{
sum += a[j];
p[i][j] = sum;
}
}
}
int main()
{
while(~scanf("%d",&n))
{
for(int i = 1; i <= n; i++)
scanf("%d",&a[i]);
init();
// 小區間 到 大區間
// dp[i][j] = dp[i][k]+dp[k+1][j]+a[k]+a[k+1];
memset(dp,0,sizeof(dp));
for(int len = 1; len < n; len++)
{
for (int i = 1; i+len <= n; i++)
{
int r = i, l = i+len;
dp[r][l] = INF;
for(int k = r; k < l; k++)
dp[r][l] = min(dp[r][l],dp[r][k]+dp[k+1][l]+p[r][k]+p[k+1][l]);
}
}
printf("%d\n",dp[1][n]);
}
return 0;
}
版本二:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define N 110
int a[N];
int dp[N][N];
int sum[N];
int main()
{
int n;
scanf("%d",&n);
memset(dp,0,sizeof(dp));
memset(sum,0,sizeof(sum));
for(int i = 0; i < n; i++)
{
scanf("%d",&a[i]);
sum[i] = i==0?a[i]:a[i]+sum[i-1];
}
for(int i = n-1; i >= 0; i--)
{
for(int j = i; j < n; j++)
{
if(i == j) dp[i][j] = 0;
else
{
dp[i][j] = INF;
int x = sum[j] - (i==0?0:sum[i-1]);
for(int k = i; k <= j; k++)
dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j]+x);
}
//printf("%d ",dp[i][j]);
}
//puts("");
}
printf("%d\n",dp[0][n-1]);
return 0;
}