一、题目
二、解题
1.题目
使用动态规划实现了压缩编码的最小化。它将问题分解为子问题,并且使用递归式来计算问题规模更小的情况。
具体来说,这个问题是给定一个长度为n的字符流,每个字符的取值为正整数,需要对其进行压缩编码。其中,如果相邻的两个字符取值相同,则它们可以合并成一组。合并后的组数目越少,则编码长度越短,需要消耗的存储空间更少。因此,本问题的目标是找到一种最优的编码方式,使编码长度最短。
设dp[i][j]表示将区间[i,j]内的字符进行编码的最小长度。其中,区间[i,j]表示字符流中第i个字符到第j个字符构成的区间。为了达到最优解,我们需要计算每个区间的最小编码长度,并且从较小的区间一步步计算出较大的区间的最小编码长度。
首先,我们需要初始化dp[i][i] = 0,表示一个字符肯定可以组成一个编码。然后,根据合并相同字符的性质,我们可以很容易地得出dp[i][j]的递归式:dp[i][j] = min{dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1]} ,其中,i ≤ k < j,sum[j] - sum[i-1]表示区间[i,j]内字符的和。这个式子的意义是,将区间[i,j]内的字符分为两部分,分别进行编码,最后将两者合并。假设分割点为k,则区间[i,k]的编码长度为dp[i][k],区间[k+1,j]的编码长度为dp[k+1][j],最后合并后的编码长度为两者的和再加上区间[i,j]内所有字符的和。
在转移的同时,我们还需要记录每个区间的分割点p[i][j]。这样,我们就可以从较小的区间开始,逐步计算出较大的区间的最优解,最终得到dp[1][n]的值,即整个字符流的最小编码长度。
2.代码
dev c++ 5.11
//压缩编码
#include<iostream>
#include<cstring>
using namespace std;
const int N=0x7F7F7F7F; //赋值给常量N一个较大的值
const int M=1000; //赋值给常量M一个较小的值
int dp[M+1][M+1]; //动态规划的二维数组
int v[M+1]; //存储压缩编码的数组
int sum[M+1]; //前缀和数组,sum[i] 存储前 i 个元素的和
int p[M+1][M+1]; //记录最优解的选择位置
int main(){
int n;
memset(dp,N,sizeof(dp)); //将dp数组中所有值赋为N
cin>>n;
sum[0]=0;
for(int i=1;i<=n;i++){
cin>>v[i]; //输入v数组的元素值
sum[i]=sum[i-1]+v[i]; //计算前缀和
dp[i][i]=0; //初始化dp数组,dp[i][i]=0
p[i][i]=i; //初始化p数组,p[i][i]=i
}
for(int len=2;len<=n;len++){ //枚举子区间的长度len,从2开始,到n止
for(int i=1;i+len-1<=n;i++){ //枚举区间的起点i,并保证终点j不超过n
int j=i+len-1; //j为区间终点
for(int k=p[i][j-1];k<=p[i+1][j];k++){ //枚举最后一个区间的终点k,这里利用了最优解结构的性质
int val=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]; //计算区间[i,j]的压缩长度
if(dp[i][j]>val){ //如果计算得到的压缩长度小于当前最优解,则更新dp数组和p数组
dp[i][j]=val;
p[i][j]=k;
}
}
}
}
cout<<dp[1][n]<<endl; //输出最终的压缩长度
return 0;
}
3.提交结果
总结
1.解释
int val=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];
的含义
在这个程序中,语句 int val=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]; 用于计算区间 [i, j] 中所有数字的和的最小值。
具体来说,这行代码中的变量含义如下:
- val:表示区间 [i,j] 中所有数的和的最小值。
- dp[i][k] 和 dp[k+1][j]:表示分别左右两侧子区间 [i, k] 和 [k+1, j] 中所有数字的和的最小值。这些值事先在程序的第一个循环中计算好并保存在 dp 数组中。
- sum[j] - sum[i-1]:表示区间 [i, j] 中所有数字的和。这个值事先也在第一个循环中计算好并保存在 sum 数组中。
因此,val 实际上就是「左侧区间和」、「右侧区间和」和「整个区间和」三者之和的最小值,其中「左侧区间和」和「右侧区间和」是固定不变的,而「整个区间和」则是随着 k 的变化而变化的。程序通过枚举所有可能的 k 值,并取最小值来得出整个区间的最小值。
- 补充
这个代码是求解区间动态规划问题,通过填写一个二维数组dp[i][j]来表示区间[i,j]的问题的最优解,其中dp[i][j]是区间[i,j]问题的最小代价。p[i][j]是在求解过程中分割点的位置,是用来记录i到j区间最优解对应的分割点。
具体来说,在i=1,len=1时,枚举区间长度,i=1,j=1,所以可以确定dp[1][1]为0,即i=j的情况下,这个区间最小代价为0。
接着,在枚举区间长度len的基础上,枚举区间左端点i,右端点j=i+len-1,然后枚举区间[i,j]分割点k的位置,以得到最优解。
所以 int i=1;i+len-1<=n;i++ 循环的作用是:遍历所有区间长度为len的区间,让i从1到n-len+1,这样就可以覆盖所有区间长度为len的区间。
接下来,对于每个区间[i,j],枚举区间分割点k,k的范围为[p[i][j-1],p[i+1][j]],其中p[i][j-1]记录的是i到j-1区间的最优分割点,p[i+1][j]记录的是i+1到j区间的最优分割点。这个范围是为了优化枚举的时间,可以直接从前一次的最优解所在位置开始枚举,到后一次的最优解所在位置为止,即使区间[i,j]最优解不在这个范围内,在下一次枚举时也会被覆盖到。
例如,当 i=1,j=4 时,p[i][j-1]=p[1][3]=2,p[i+1][j]=p[2][4]=3,所以k的范围为2到3,枚举k可以得到区间[1,4]的最优解。