【CCF-CSP】 201612-4 压缩编码 DP


一、题目

在这里插入图片描述

原题目链接

二、解题

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.解释

  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 值,并取最小值来得出整个区间的最小值。

  1. 补充

这个代码是求解区间动态规划问题,通过填写一个二维数组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]的最优解。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值