CCF-CSP201612-4 压缩编码(100分)(动态规划+平行四边形不等式优化详解)

题目

试题编号 201612-4
试题名称 压缩编码
时间限制 3s
内存限制 256.0MB
问题描述  给定一段文字,已知单词a1, a2, …, an出现的频率分别t1, t2, …, tn。可以用01串给这些单词编码,即将每个单词与一个01串对应,使得任何一个单词的编码(对应的01串)不是另一个单词编码的前缀,这种编码称为前缀码。
 使用前缀码编码一段文字是指将这段文字中的每个单词依次对应到其编码。一段文字经过前缀编码后的长度为:
L=a1的编码长度×t1+a2的编码长度×t2+…+ an的编码长度×tn。
定义一个前缀编码为字典序编码,指对于1 ≤ i < n,ai的编码(对应的01串)的字典序在ai+1编码之前,即a1, a2, …, an的编码是按字典序升序排列的。
例如,文字E A E C D E B C C E C B D B E中, 5个单词A、B、C、D、E出现的频率分别为1, 3, 4, 2, 5,则一种可行的编码方案是A:000, B:001, C:01, D:10, E:11,对应的编码后的01串为1100011011011001010111010011000111,对应的长度L为3×1+3×3+2×4+2×2+2×5=34。
在这个例子中,如果使用哈夫曼(Huffman)编码,对应的编码方案是A:000, B:01, C:10, D:001, E:11,虽然最终文字编码后的总长度只有33,但是这个编码不满足字典序编码的性质,比如C的编码的字典序不在D的编码之前。
在这个例子中,有些人可能会想的另一个字典序编码是A:000, B:001, C:010, D:011, E:1,编码后的文字长度为35。
请找出一个字典序编码,使得文字经过编码后的长度L最小。在输出时,你只需要输出最小的长度L,而不需要输出具体的方案。在上面的例子中,最小的长度L为34。
输入格式 输入的第一行包含一个整数n,表示单词的数量。
第二行包含n个整数,用空格分隔,分别表示a1, a2, …, an出现的频率,即t1, t2, …, tn。请注意a1, a2, …, an具体是什么单词并不影响本题的解,所以没有输入a1, a2, …, an。
输出格式 输出一个整数,表示文字经过编码后的长度L的最小值。
样例输入 5
1 3 4 2 5
样例输出 34
样例说明 这个样例就是问题描述中的例子。如果你得到了35,说明你算得有问题,请自行检查自己的算法而不要怀疑是样例输出写错了。
评测用例规模与约定 对于30%的评测用例,1 ≤ n ≤ 10,1 ≤ ti ≤ 20;
对于60%的评测用例,1 ≤ n ≤ 100,1 ≤ ti ≤ 100;
对于100%的评测用例,1 ≤ n ≤ 1000,1 ≤ ti ≤ 10000。

题目解析

首先这道题还是蛮良心的,因为担心我们会想到哈夫曼编码还在题目给提了出来,哈夫曼编码我们都很熟悉,用优先队列循环处理就可以了,但是这个题要我们保持字典序的大小,因为哈夫曼是贪心策略进行不管字典序,所以本题我们不能从这里去想。仔细想来这个题目就是石子排序的变形。
既然是石子排序的原型,那么我们就知道本题要用动态规划来做,这是一个很基础的动态规划:

dp[i][j]:表示的就是将第i个单词到第j个单词编码后的长度L。

初始化dp[i][i]肯定是0,用sum[k]表示前k个单词的编码后长度。
所以有状态转移方程:

dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1])

要将第i到第j个单词合并成一个,由于字典序只能合并相邻的,所以只能先合并成i到k,k+1到j两个,再把i到k和k+1到j合并成一个。为了得到最优解,要把每种可能取值的k都找一遍,取最小值。

AC的程序

在这里插入图片描述

#include <iostream>
#include <stdio.h>
#include <cstring>
#define mem(a) memset(a,0,sizeof(a))
#define mems(a) memset(a,0x3f,sizeof(a))
#define rep(i,a,n) for(int i=a;i<n;i++)
#define min(a,b) ((a)>(b)?(b):(a))
using namespace std;
int a[1005];
int dp[1005][1005];
int sum[1005];
int main(){
	int n;
	scanf("%d",&n);
	mem(sum);
	mems(dp);
	rep(i,0,n)
		scanf("%d",&a[i]);
	sum[0]=a[0];
	dp[0][0]=0;
	rep(i,1,n){
		dp[i][i]=0;
		sum[i]=sum[i-1]+a[i];
	}
	rep(v,1,n){
		rep(i,0,n-v){
			int j=i+v;
			rep(k,i,j+1){
				dp[i][j]=min(dp[i][j],(dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]));
			}			
		}
	}
	printf("%d\n",dp[0][n-1]);
	return 0;
}

可以很明显的看到这个程序要进行一个半数矩阵处理,for一次i还要for一次j,每次还要找k,总共的复杂度是: O ( n 3 ) O(n^3) O(n3)



平行四边形优化dp

首先要先了解什么是四边形不等式,这个里面说的比较稳妥了,我再稍作解释:
对于一个权重函数m[i][j],若有:
m [ i ] [ j ] + m [ i ′ ] [ j ′ ] ≤ m [ i ] [ j ′ ] + m [ i ′ ] [ j ] ( i ≤ i ′ ≤ j ≤ j ′ ) m[i][j]+m[i'][j']≤m[i][j']+m[i'][j](i≤i'≤j≤j') m[i][j]+m[i][j]m[i][j]+m[i][j](iijj)
那么这一个权重函数m满足四边形不等式。
CCF-CSP201612-4 压缩编码(100分)(动态规划+平行四边形不等式优化详解)_第1张图片
就是像本题一样的一种状态转移:
d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , ( d p [ i ] [ k ] + d p [ k + 1 ] [ j ] + c o s t [ i ] [ j ] ) dp[i][j]=min(dp[i][j],(dp[i][k]+dp[k+1][j]+cost[i][j]) dp[i][j]=min(dp[i][j],(dp[i][k]+dp[k+1][j]+cost[i][j])
这种O(n^3)复杂度的程序往往是不被接受的,那么我们就要想办法来降低它的复杂度,这种dp的关键是找到一个称之为合适的k的位置,要是找到了这种位置我们就可以大大减小运算量。
我们这里设s[i][j]为dp[i][j]取得的合适的k值,即:
s [ i ] [ j ] = d p [ i ] [ j ] k ( m i n ( d p [ i ] [ j ] ) = d p [ i ] [ s [ i ] [ j ] ] + d p [ s [ i ] [ j ] + 1 ] [ j ] + c o s t [ i ] [ j ] ) s[i][j]=dp[i][j]^k(min(dp[i][j])=dp[i][s[i][j]]+dp[s[i][j]+1][j]+cost[i][j]) s[i][j]=dp[i][j]k(min(dp[i][j])=dp[i][s[i][j]]+dp[s[i][j]+1][j]+cost[i][j])

给出两个定理

1.如果cost函数满足四边形不等式,那么dp也满足四边形不等式。
2.如果dp满足四边形不等式性质,那么s[i][j]权重函数单调,有如下规则:
s [ i ] [ j − 1 ] ≤ s [ i ] [ j ] ≤ s [ i + 1 ] [ j ] s[i][j-1]≤s[i][j]≤s[i+1][j] s[i][j1]s[i][j]s[i+1][j]

下面开始证明

section 1:证明cost满足不等式性质

CCF-CSP201612-4 压缩编码(100分)(动态规划+平行四边形不等式优化详解)_第2张图片
这个矩阵展示了我们要处理的dp数组的顺序,因为是半数dp所以红色部分不管,那么我们对于所有的i和j肯定有:
令 i < i + 1 < j < j + 1. 要 证 明 : c o s t [ i ] [ j ] + c o s t [ i + 1 ] [ j + 1 ] ≤ c o s t [ i ] [ j + 1 ] + c o s t [ i + 1 ] [ j ] 移 项 后 有 : c o s t [ i ] [ j ] − c o s t [ i + 1 ] [ j ] ≤ c o s t [ i ] [ j + 1 ] − c o s t [ i + 1 ] [ j + 1 ] 设 f ( j ) = c o s t [ i ] [ j ] − c o s t [ i + 1 ] [ j ] , 只 要 证 明 f [ j ] 为 单 调 增 函 数 即 可 令ii<i+1<j<j+1.cost[i][j]+cost[i+1][j+1]cost[i][j+1]+cost[i+1][j]cost[i][j]cost[i+1][j]cost[i][j+1]cost[i+1][j+1]f(j)=cost[i][j]cost[i+1][j]f[j]

section 2:证明dp满足不等式性质

这里要证明dp满足平行四边形不等式性质,仍然对于i和j有如下规则:
i < i + 1 < j < j + 1. ii<i+1<j<j+1.
要证明: d p [ i ] [ j ] + d p [ i + 1 ] [ j + 1 ] ≤ d p [ i ] [ j + 1 ] + d p [ i + 1 ] [ j ] dp[i][j]+dp[i+1][j+1]≤dp[i][j+1]+dp[i+1][j] dp[i][j]+dp[i+1][j+1]dp[i][j+1]+dp[i+1][j]
令dp[i+1][j]取得最优值的时候k=x,即dp[i+1][j]^{k=x};
令dp[i][j+1]取得最优值的时候k=y,即dp[i][j+1]^{k=y};
这里要讨论x≤y和x>y两种情况下,方法类似,本文只述前者
为了拼凑出cost的不等式,我们把k=x作为dp[i][j]分界,把k=y作为dp[i+1][j+1]分界(对于x>y的时候把分解反过来带入),有如下式子:
d p [ i ] [ x ] + d p [ x + 1 ] [ j ] + c o s t [ i ] [ j ] + d p [ i + 1 ] [ y ] + d p [ y + 1 ] [ j + 1 ] + c o s t [ i + 1 ] [ j + 1 ] dp[i][x]+dp[x+1][j]+cost[i][j]+dp[i+1][y]+dp[y+1][j+1]+cost[i+1][j+1] dp[i][x]+dp[x+1][j]+cost[i][j]+dp[i+1][y]+dp[y+1][j+1]+cost[i+1][j+1]
将cost的不等式带入后有:
d p [ i ] [ x ] + d p [ x + 1 ] [ j ] + c o s t [ i ] [ j ] + d p [ i + 1 ] [ y ] + d p [ y + 1 ] [ j + 1 ] + c o s t [ i + 1 ] [ j + 1 ] ≤ d p [ i ] [ x ] + d p [ x + 1 ] [ j ] + c o s t [ i ] [ j + 1 ] + d p [ i + 1 ] [ y ] + d p [ y + 1 ] [ j + 1 ] + c o s t [ i + 1 ] [ j ] ≤ d p [ i ] [ x ] + d p [ x + 1 ] [ j ] + c o s t [ x + 1 ] [ j + 1 ] + c o s t [ i ] [ j + 1 ] + d p [ i + 1 ] [ y ] + d p [ y + 1 ] [ j + 1 ] − c o s t [ y + 1 ] [ j + 1 ] + c o s t [ i + 1 ] [ j ] ≤ d p [ i ] [ j + 1 ] + d p [ i + 1 ] [ j ] dp[i][x]+dp[x+1][j]+cost[i][j]+dp[i+1][y]+dp[y+1][j+1]+cost[i+1][j+1] \\≤ dp[i][x]+dp[x+1][j]+cost[i][j+1]+dp[i+1][y]+dp[y+1][j+1]+cost[i+1][j] \\≤ dp[i][x]+dp[x+1][j]+cost[x+1][j+1]+cost[i][j+1]+dp[i+1][y]\\+dp[y+1][j+1]-cost[y+1][j+1]+cost[i+1][j] \\≤ dp[i][j+1]+dp[i+1][j] dp[i][x]+dp[x+1][j]+cost[i][j]+dp[i+1][y]+dp[y+1][j+1]+cost[i+1][j+1]dp[i][x]+dp[x+1][j]+cost[i][j+1]+dp[i+1][y]+dp[y+1][j+1]+cost[i+1][j]dp[i][x]+dp[x+1][j]+cost[x+1][j+1]+cost[i][j+1]+dp[i+1][y]+dp[y+1][j+1]cost[y+1][j+1]+cost[i+1][j]dp[i][j+1]+dp[i+1][j]
得 证 : d p [ i ] [ j ] + d p [ i + 1 ] [ j + 1 ] ≤ d p [ i ] [ j + 1 ] + d p [ i + 1 ] [ j ] 得证:dp[i][j]+dp[i+1][j+1]≤dp[i][j+1]+dp[i+1][j] dp[i][j]+dp[i+1][j+1]dp[i][j+1]+dp[i+1][j]
这上面这一步加上cost[x-1][j+1]再减去cost[y+1][j+1],因为x≤y,故这一步放缩符号是传递的,做这两部操作是为了拼凑dp不等式右侧的值,这里要注意到同时还要加dp[j+1][j+1]再减去dp[j+1][j+1],抵消了,但因为都是0也无所谓抵消不抵消,可以自己推导下,还是不清楚欢迎给我留言,因为我看其他的博客这里放缩是一步到位,有点解释不清。

section 3:证明s[i][j]单调

要证明: s [ i ] [ j − 1 ] ≤ s [ i ] [ j ] ≤ s [ i + 1 ] [ j ] s[i][j-1]≤s[i][j]≤s[i+1][j] s[i][j1]s[i][j]s[i+1][j]
我们先证明左侧不等式:
s [ i ] [ j − 1 ] ≤ s [ i ] [ j ] s[i][j-1]≤s[i][j] s[i][j1]s[i][j]
方法比较巧妙,请仔细:
令dp[i][j-1]取得最小值时的k=y,对于所有x≠y,令x<=y
有如下情况:
x + 1 ≤ y + 1 ≤ j − 1 ≤ j x+1≤y+1≤j-1≤j x+1y+1j1j
将刚刚证明的dp四边形不等式拿出来:
d p [ x + 1 ] [ j − 1 ] + d p [ y + 1 ] [ j ] ≤ d p [ x + 1 ] [ j ] + d p [ y + 1 ] [ j − 1 ] dp[x+1][j-1]+dp[y+1][j]≤dp[x+1][j]+dp[y+1][j-1] dp[x+1][j1]+dp[y+1][j]dp[x+1][j]+dp[y+1][j1]
两侧加上dp[i][x]+cost[i][j-1]+dp[i][y]+cost[i][j],得:
d p [ i ] [ j − 1 ] k = x + d p [ i ] [ j ] k = y ≤ d p [ i ] [ j ] k = x + d p [ i ] [ j − 1 ] k = y dp[i][j-1]^{k=x}+dp[i][j]^{k=y}≤dp[i][j]^{k=x}+dp[i][j-1]^{k=y} dp[i][j1]k=x+dp[i][j]k=ydp[i][j]k=x+dp[i][j1]k=y
进一步得到:
d p [ i ] [ j − 1 ] k = x − d p [ i ] [ j − 1 ] k = y ≤ d p [ i ] [ j ] k = x − d p [ i ] [ j ] k = y dp[i][j-1]^{k=x}-dp[i][j-1]^{k=y}≤dp[i][j]^{k=x}-dp[i][j]^{k=y} dp[i][j1]k=xdp[i][j1]k=ydp[i][j]k=xdp[i][j]k=y
因为假设种y是dp[i][j-1]的最佳k,所以左面是≥0的,所以:
d p [ i ] [ j ] k = x − d p [ i ] [ j ] k = y ≥ 0 dp[i][j]^{k=x}-dp[i][j]^{k=y}≥0 dp[i][j]k=xdp[i][j]k=y0
可以这样来看这个不等式,对于让dp[i][j-1]取得最优值的y,所有小于y的值x都没有让dp[i][j]更优,即证明了:
s [ i ] [ j − 1 ] ≤ s [ i ] [ j ] s[i][j-1]≤s[i][j] s[i][j1]s[i][j]
对于右侧s[i][j+1]≥s[i+1][j]的证明也类似,请自己完成。
所以权重函数单调得证明,那么我们的证明就结束了。

下面开始进行本题的优化(终于写完了…)

我们本题有如下性质:
c o s t [ i ] [ j ] = s u m [ j ] − s u m [ i − 1 ] cost[i][j]=sum[j]-sum[i-1] cost[i][j]=sum[j]sum[i1]
所以对于证明f(j)=cost[i][j]-cost[i+1][j]单调就简单的多了:
f ( j ) = c o s t [ i ] [ j ] + c o s t [ i + 1 ] [ j ] = s u m [ j ] − s u m [ i − 1 ] − s u m [ j ] + s u m [ i ] = s u m [ i ] − s u m [ i − 1 ] = a [ i ] f(j)=cost[i][j]+cost[i+1][j]\\=sum[j]-sum[i-1]-sum[j]+sum[i]\\ =sum[i]-sum[i-1]=a[i] f(j)=cost[i][j]+cost[i+1][j]=sum[j]sum[i1]sum[j]+sum[i]=sum[i]sum[i1]=a[i]
这肯定是满足不等式性质的了,那么后面的定理都可以用了。

AC的程序

在这里插入图片描述

#include <iostream>
#include <stdio.h>
#include <cstring>
#define mem(a) memset(a,0x3f,sizeof(a))
#define rep(i,a,n) for(int i=a;i<n;i++)
using namespace std;
int a[1001];
int dp[1001][1001],s[1001][1001];
int sum[1005];
int main(){
	int n;
	mem(dp);
	scanf("%d",&n);
	rep(i,0,n)
		scanf("%d",&a[i]);
	sum[0]=a[0];
	dp[0][0]=s[0][0]=0;
	rep(i,1,n){
		dp[i][i]=0;
		s[i][i]=i;
		sum[i]=sum[i-1]+a[i];
	}
	rep(w,1,n){
		rep(i,0,n-w+1){
			int j=i+w;
			rep(k,s[i][j-1],s[i+1][j]+1){
				if(dp[i][j]>dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]){
					dp[i][j]=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];
					s[i][j]=k;
				}
			}
		}
	}
	printf("%d\n",dp[0][n-1]);
	return 0;
} 

复杂度分析

可以看到时间上有了极大改善,这是为什么呢?其实外侧仍然是for一次i再for一次j,那么内部的运行次数取决于s[i][j-1]和s[i+1][j]的值,我来详细解释一下把,因为很多博客都是照搬了百度上的公式让人很难理解:
首先我们可以看到外侧的确是for了一遍i,那么内侧呢?实际上他是进行了左上到右下的斜线(\)扫描,自然计算的是:
d p [ 0 ] [ L ] , d p [ 1 ] [ L + 1 ] , . . . , d p [ n − L − 1 ] [ n − 1 ] dp[0][L],dp[1][L+1],...,dp[n-L-1][n-1] dp[0][L],dp[1][L+1],...,dp[nL1][n1]
计算的和就是:
( s [ 1 ] [ L ] − s [ 0 ] [ L − 1 ] ) + ( s [ 2 ] [ L + 1 ] − s [ 1 ] [ L ] ) + . . . + ( s [ n − L ] [ n − 1 ] − s [ n − L − 1 ] [ n − 2 ] ) (s[1][L]-s[0][L-1])+(s[2][L+1]-s[1][L])+...+(s[n-L][n-1]-s[n-L-1][n-2]) (s[1][L]s[0][L1])+(s[2][L+1]s[1][L])+...+(s[nL][n1]s[nL1][n2])
结果运算结果是:
s [ n − L ] [ n − 1 ] − s [ 0 ] [ L − 1 ] ≤ n s[n-L][n-1]-s[0][L-1]≤n s[nL][n1]s[0][L1]n
故时间复杂度肯定在:
O ( n 2 ) O(n^2) O(n2)
其实在我们实际应用中进行一系列证明的确麻烦,也没有时间,多数情况是有这个猜想然后打表看一下就好了,有错误或者想法欢迎留言!

你可能感兴趣的:(csp,动态规划,算法,数据结构)