J Pangu and Stones
题意:有n个数字,每次可以选择将k个连续的数字合并成一个数,k
∈
[l,r],每次合并的代价是合并数的和。问将所有数合并成一个数的最小代价和是多少,如果不能全部合并成一个数,就输出0;
样例:
input:
3 2 2
1 2 3
output:
9
hint:
3个数字,l=2,r=2;
3个数字分别是1,2,3
合并两次
1,2合并为3,代价3
3 3合并成6 代价为3+6=9
分析
区间dp;
这种典型连续问题就是dp吖;
感觉难点是状态要找对..
开始想的是dp[i][j][k]:表示每次合并k,下标从i到j的数字合并起来的代价
然后不会写状态转移了
但是换一个想法,dp[i][j][k]:表示下标从i到j的数字可以分成k堆。
状态转移就好写多了~
dp[i][j][k]=dp[i][j][t]+dp[t+1][j][1];
因为要由已知推未知,所以这里的循环顺序不要搞反了..
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
const int maxn = 150;
int dp[maxn][maxn][maxn];
int sum[maxn];
int main()
{
int n,l,r;
while(scanf("%d %d %d",&n,&l,&r)!=EOF)
{
mem(dp,-1);mem(sum,0);
for(int i=1;i<=n;i++)
{
scanf("%d",&sum[i]);
sum[i]+=sum[i-1];
}
for(int len = 1;len<=n;len++)
{
for(int i=1;i<=n;i++)
{
int j = i+len-1;
if(j>n) break;
dp[i][j][len]=0;
for(int k=len-1;k>=2;k--)
{
for(int t=i;t<j;t++){
if(dp[i][t][k-1]!=-1&&dp[t+1][j][1]!=-1){
if(dp[i][j][k]==-1) dp[i][j][k]=dp[i][t][k-1]+dp[t+1][j][1];
else dp[i][j][k]=min(dp[i][j][k],dp[i][t][k-1]+dp[t+1][j][1]);
}
}
}
for(int k=l;k<=r;k++)
{
if(dp[i][j][k]!=-1){
if(dp[i][j][1]==-1) {dp[i][j][1]=dp[i][j][k]+sum[j]-sum[i-1];continue;}
dp[i][j][1]=min(dp[i][j][1],dp[i][j][k]+sum[j]-sum[i-1]);
}
}
}
}
if(dp[1][n][1]==-1) printf("0\n");
else printf("%d\n",dp[1][n][1]);
}
return 0;
}
/*
7 3 3
1 1 1 5 1 1 1
*/