CCF-CSP 201612-4壓縮編碼解題報告
CCF-CSP 201612-4壓縮編碼
標籤(空格分隔): CCF-CSP
1、問題描述
2、題解
(1)引——石子合併問題
題目描述:在一個操場上擺放著一行共n堆的石子。現要將石子有序地合併成一堆。規定每次只能選相鄰的兩堆合併成新的一堆,並將新的一堆石子數記為該次合併的得分。請計算出將n堆石子合併成一堆的最小得分。
題解:
人們的第一想法往往是貪心(找最大/最小的堆合併),但是很容易找到貪心的反例:
- 貪心:
3 4 6 5 4 2
5 4 6 5 4 得分:5
9 6 5 4 得分:9
9 6 9 得分:9
15 9 得分:15
24 得分:24
總得分:62 - 合理方案:
3 4 6 5 4 2
7 6 5 4 2 得分:7
7 6 5 6 得分:6
7 11 6 得分:11
13 11 得分:13
24 得分:24
總得分:61
- 貪心:
顯然利用貪心來做是錯誤的,貪心演算法在子過程中得出的解只是區域性最優,而不能保證使得全域性的值最優。
如果N-1次合併的全域性最優解包含了每一次合併的子問題的最優解,那麼經這樣的N-1次合併後的得分總和必然是最優的。因此我們需要通過動態規劃演算法來求出最優解。
在此我們假設有n堆石子,一字排開,合併相鄰兩堆的石子,每合併兩堆石子得到一個分數,最終合併後總分數最少的。
我們設dp[i][j]定義為第i堆石子到第j堆石子合併後的最少總分數,sum[i][j]為第i堆到第j堆石子的總數,a[i]為第i堆石子得石子數量。
當合並的石子堆為1堆時,很明顯dp[i][i]的分數為0;
當合並的石子堆為2堆時,dp[i][i+1]的分數為a(i)+a(i+1);
當合並的石子堆為3堆時,dp[i][i+2]的分數為Min((dp[i][i]+dp[i+1][i+2]+sum[i][i+2]),(dp[i][i+1]+dp[i+2][i+2]+sum[i][i+2]));
當合並的石子堆為4堆時……
可總結出動態轉移方程:dp[i][j] = min{ dp[i][k]+dp[k+1][j]+sum[i][j] } (i <= k < j)
(2)壓縮編碼
問題分析:首先要清楚huffman編碼的原理,然後要在huffman的基礎上保證有序,所以合併過程從huffman的合併最小的兩個變成了合併相鄰兩個讓其總和最小,轉化成石子合併問題,每堆石子的數量相當於字母出現的頻率。
時間複雜度:O(n^3),理論上可以得60分 (實際上,如果CCF的資料比較弱的話可以得滿分,親測2.437s過)。時間優化:四邊形優化
詳細證明:傳送門
設m[i,j]表示動態規劃的狀態量。
m[i,j]有類似如下的狀態轉移方程:
m[i,j]=min{m[i,k]+m[k,j]}(i≤k≤j)
m滿足四邊形不等式是適用這種優化方法的必要條件
定義s(i,j)為函式m(i,j)對應的使得m(i,j)取得最大值的k值。
我們可以證明,s[i,j-1]≤s[i,j]≤s[i+1,j]
那麼改變狀態轉移方程為:
m[i,j]=min{m[i,k]+m[k,j]} (s[i,j-1]≤k≤s[i+1,j])
時間複雜度:O(n^2),可以得100分(親測46ms過)。
3、程式
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MAXN=1007;
const LL oo=(1LL<<60);
int n,s[MAXN][MAXN];
LL a[MAXN],dp[MAXN][MAXN],sum[MAXN];
int main() {
int i,len,j,k,mink;
LL tmp;
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%lld",&a[i]);
sum[0]=0;
for(i=1;i<=n;i++){
sum[i]=sum[i-1]+a[i];
s[i][i]=i;
}
for(len=1;len<n;len++) {
for(i=1;i+len<=n;i++) {
j=i+len;
tmp=oo;
mink=i;
for(k=s[i][j-1];k<=s[i+1][j];k++) {
if(dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]<tmp){
tmp=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];
mink=k;
}
}
dp[i][j]=tmp;
s[i][j]=mink;
}
}
printf("%lld\n",dp[1][n]);
return 0;
}