一、石子合并1
题意:相邻的两堆石子合并成一堆,每次合并需要的代价是合并的两队石子数量之和
和哈夫曼树的区别是:加上了只能合并次昂林两个数的限制条件
所有合并方式里代价的最小值
最后一次一定是将两堆合并成一堆
以最后一次合并的分界线分类,确定最后一次状态的集合(枚举最后一次合并)
区间dp模板
所有合并过程的代价之和的最小值=最后一次合并的代价+之前合并花费的代价的最小值。(所有数加上一个数,原来的最小值依然是最小值)
- 区间dp一般都是较大的区间由较小的区间递推而来,因此 区间长度要按从小到大枚举
- 有点分治的思想,状态转移方程是按照枚举最后一次区间的分割点得到的若干种情况的集合
- 一般分治还会用到前缀和/积 等等
- 求最小值要将区间dp值初始化为无穷大,求最大值看情况是0还是负无穷
or (int len = 1; len <= n; len++) { // 区间长度
for (int i = 1; i + len - 1 <= n; i++) { // 枚举起点
int j = i + len - 1; // 区间终点
if (len == 1) {
dp[i][j] = 初始值
continue;
}
for (int k = i; k < j; k++) { // 枚举分割点,构造状态转移方程
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + w[i][j]);
}
}
}
区间dp
#include <iostream>
#include <string.h>
using namespace std;
const int N=305;
int a[N];
int s[N];
int dp[N][N];
signed main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
s[i]=s[i-1]+a[i];
}
// memset(dp,0x3f,sizeof(dp));
// for(int i=1;i<=n;i++) dp[i][i]=0;
// for(int i=1;i<n;i++){
// for(int j=i+1;j<=n;j++){//求区间[i,j]石子合并所需要的最小代价
// for(int k=i;k<=j-1;k++){
// dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+s[j]-s[i-1]);
// }
// }
// }
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;i++) dp[i][i]=0;
// 由于采用递推方式,较大的区间要由较小的区间递推而来,
// 所以要按照区间长度从小到大来枚举
// 普通地枚举起点和终点,依次得到的区间长度并不是从小到大
for(int len=1;len<=n;len++){//枚举区间长度
for(int i=1;i+len-1<=n;i++){//枚举起点
int j=i+len-1;//对应的区间终点
// if(len==1)dp[i][j]=0;//其实已经初始化过了,len可从2枚举
for(int k=i;k<=j-1;k++){
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+s[j]-s[i-1]);
}
}
}
cout<<dp[1][n];
return 0;
}
记忆化递归做法
#include <iostream>
#include <string.h>
using namespace std;
const int N=305;
const int inf=0x3f3f3f3f;
int a[N];
int s[N];
int dp[N][N];
int f(int l,int r){
if(dp[l][r]!=-1)return dp[l][r];
if(l==r)return 0;
dp[l][r]=inf;
for(int k=l;k<=r-1;k++){//[l,r]区间最后一次合并的分界点
dp[l][r]=min(dp[l][r],f(l,k)+f(k+1,r)+s[r]-s[l-1]);
}
return dp[l][r];
}
signed main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
s[i]=s[i-1]+a[i];
}
fill(dp[0],dp[0]+N*N,-1);
cout<<f(1,n);
return 0;
}
二、快快变大
区间dp
分治思想,假设所有的元素已经合并成了两个区间(或者说是合并成了两个数,某个区间所有元素合并成一个数,这个数等于该区间所有元素的乘积),还剩最后一次合并就能把所有元素合并成一个,接下来要做的事儿就是枚举最后这两个区间的分界线
#include <iostream>
#include <string.h>
using namespace std;
#define int long long
const int N=305;
const int mod=1000003;
int a[N];
int mul[N][N];
int dp[N][N];
signed main(){
//将相邻两个元素合并成他们的乘积,得到相应的分数(差值的平方)
//相邻的两个元素合并,分治思想 ,小区间合并成大区间
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
mul[i][i]=a[i];
for(int j=i+1;j<=n;j++){
mul[i][j]=mul[i][j-1]*a[j]%mod;
}
}//[i,j]这个区间的所有元素的乘积
for(int len=1;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int j=i+len-1;
for(int k=i;k<=j-1;k++){
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+(mul[i][k]-mul[k+1][j])*(mul[i][k]-mul[k+1][j]));
//注意啦,这里将两个数(2个区间)合并是合并成它们的乘积而非和
//[i,j]这个区间dp值是由已经合并好的区间[i,k]和[k+1,j]这两个合并好的
//区间,合并好的区间相当于这个区间所有数的乘积
}
}
}
cout<<dp[1][n];
return 0;
}
记忆化递归做法
#include <iostream>
#include <string.h>
using namespace std;
#define int long long
const int N=305;
const int mod=1000003;
int a[N];
int mul[N][N];
int dp[N][N];
int f(int l,int r){
int& v=dp[l][r];
if(v!=-1)return v;
if(l==r)return 0;
for(int k=l;k<=r-1;k++){
v=max(v,f(l,k)+f(k+1,r)+(mul[l][k]-mul[k+1][r])*(mul[l][k]-mul[k+1][r]));
}
return v;
}
signed main(){
//将相邻两个元素合并成他们的乘积,得到相应的分数(差值的平方)
//相邻的两个元素合并,分治思想 ,小区间合并成大区间
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
mul[i][i]=a[i];
for(int j=i+1;j<=n;j++){
mul[i][j]=mul[i][j-1]*a[j]%mod;
}
}//[i,j]这个区间的所有元素的乘积
fill(dp[0],dp[0]+N*N,-1);
cout<<f(1,n);
return 0;
}