算法总结—区间DP1
石子合并(弱化版)
状态
\(dp_{i,j}\) 表示把 \([i,j]\) 合成一堆所需要的最小代价。
答案
最小代价显然是 \(dp_{1,n}\)。
状态转移方程
可以把区间 \([i,j]\) 分成 两段 \([i,k],[k+1,j](i\leq k<j)\),所以 \(dp_{i,j}=\min\{dp_{i,k}+dp_{k+1,j}+sum_{j}-sum_{i-1}\}\)。注意是先枚举区间长度 \(len\),再枚举 \(i\) 并算出 \(j=i+len-1\),最后枚举 \(k\)。
初始值
因为是求最小值,所以 \(dp_{i,j}=\inf\),长度为一的区间不需要合并,所以 \(dp_{i,i}=0\)。
区间 DP 的时间复杂度是 \(\mathcal{O}(n^3)\)
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int a[305];
int sum[305];
int dp[305][305];
signed main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
sum[i]=sum[i-1]+a[i];
for(int j=1;j<=n;j++){
dp[i][j]=1e18;
}
dp[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=i;k<j;k++){
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
}
}
}
cout<<dp[1][n];
return 0;
}
Optimal Array Multiplication Sequence
已知合并一次的代价为 \(N_iM_kM_j\),但是不好记录路径对于一个区间 \([i,j]\) 可以记录最优的分段点 \(k\),再递归输出。
代码
#include<bits/stdc++.h>
using namespace std;
int n;
int Case=0;
int N[15],M[15];
int dp[15][15];
int pre[15][15];
void dfs(int l,int r){
if(l==r){
cout<<"A"<<l;
return ;
}
cout<<"(";
dfs(l,pre[l][r]);
cout<<" x ";
dfs(pre[l][r]+1,r);
cout<<")";
return;
}
void work(){
memset(dp,0x3f,sizeof dp);
for(int i=1;i<=n;i++){
cin>>N[i]>>M[i];
dp[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=i;k<j;k++){
if(dp[i][j]>dp[i][k]+dp[k+1][j]+N[i]*M[k]*M[j]){
dp[i][j]=dp[i][k]+dp[k+1][j]+N[i]*M[k]*M[j];
pre[i][j]=k;
}
}
}
}
cout<<"Case "<<++Case<<": ";
dfs(1,n);
cout<<"\n";
return;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
while(cin>>n){
if(n==0){
break;
}
work();
}
return 0;
}

浙公网安备 33010602011771号