J - Pangu and Stones 区间dp
题目大意:
给你n堆石头,对这n堆石头进行合并,要求每次合并的石头堆数不能小于 (l),也不能大于 (r) ,每次合并的代价是合并的石头的数量,问将所有的石头合并的最小代价是多少,如果无法合并成一堆则输出0。
题解:
这个和石头合并很像,但是多了一维石头的堆数,所以可以定义 (dp[i][j][k]) 表示从 (i) 到 (j) 有 (k) 堆石头的最小代价。最后答案输出 (dp[1][n][1]) 即可。
但是中间要保证每次合并数量 (x) 是在区间 ([l,r]) ,合并有两种方式:
- 第一种就是合并成一堆,这个则需要花费代价
- 第二种就是 (k) 堆由 1 和 (k-1) 组合在一起,这个不需要花费代价。为什么是 1 和 (k-1),而不是 (x) 和 (k-x) 这个应该显而易见,因为我只需要找到第一堆的最优位置即可,遍历这个 (x) 最后得到的结果其实和找到第一堆的最优位置是一样的。
转移方程:
当 (k==1) (dp[i][j][k]=min(dp[i][j][k],dp[i][x][1]+dp[x+1][j][y]+sum[j]-sum[i-1]))
其他 (dp[i][j][k]=min(dp[i][j][k],dp[i][x][1]+dp[x+1][j][k-1]))
但是注意当 (k==1) 的时候,这个转移必须合法转移,也就是合并的堆数一定要在区间 ([l,r]),所以需要一个数组来求 $dp[x+1][j][y] $ y必须满足条件 : (l-1=<y<=r-1) ,
所以就重新开一个数组 (pre[i][j]) 表示区间([l,r]) 合并的堆数是 ([l-1,r-1]) 的最小代价。
所以最后的转移方程就是:
if(k==1) dp[i][j][k]=min(dp[i][j][k],dp[i][x][1]+pre[x+1][j]+sum[j]-sum[i-1]);
else dp[i][j][k]=min(dp[i][j][k],dp[i][x][1]+dp[x+1][j][k-1]);
代码:
#include <bits/stdc++.h>
#define id first
#define val second
#define inf 0x3f3f3f3f
#define inf64 0x3f3f3f3f3f3f3f3f
using namespace std;
const int maxn=110;
typedef long long ll;
ll dp[maxn][maxn][maxn],a[maxn],sum[maxn],pre[maxn][maxn];
int main(){
int n,l,r;
while(~scanf("%d%d%d",&n,&l,&r)){
sum[0]=0;
memset(dp,inf64,sizeof(dp));
memset(pre,inf64,sizeof(pre));
dp[0][0][0]=0;
for(int i=1;i<=n;i++) {
scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i],dp[i][i][1]=0;
if(1>=l-1&&1<=r-1) pre[i][i]=0;
}
for(int len=2;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int j=i+len-1;
for(int k=1;k<=r;k++){
for(int x=i;x<=j;x++){
if(k==1) dp[i][j][k]=min(dp[i][j][k],dp[i][x][1]+pre[x+1][j]+sum[j]-sum[i-1]);
else dp[i][j][k]=min(dp[i][j][k],dp[i][x][1]+dp[x+1][j][k-1]);
if(k>=l-1&&k<=r-1) pre[i][j]=min(pre[i][j],dp[i][j][k]);
// printf("dp[%d][%d][%d]=%lld pre[%d][%d]=%lld
",i,j,k,dp[i][j][k],i,j,pre[i][j]);
}
}
}
}
ll ans=dp[1][n][1];
if(ans<inf64) printf("%lld
",ans);
else printf("0
");
}
return 0;
}