算法总结—区间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;
}
posted @ 2025-04-14 13:21  LRRabcd  阅读(10)  评论(0)    收藏  举报