1. 程式人生 > >CCF-CSP 201612-4壓縮編碼解題報告

CCF-CSP 201612-4壓縮編碼解題報告

CCF-CSP 201612-4壓縮編碼

標籤(空格分隔): CCF-CSP

1、問題描述

傳送門

2、題解

(1)引——石子合併問題

  1. 題目描述:在一個操場上擺放著一行共n堆的石子。現要將石子有序地合併成一堆。規定每次只能選相鄰的兩堆合併成新的一堆,並將新的一堆石子數記為該次合併的得分。請計算出將n堆石子合併成一堆的最小得分。

  2. 題解:

    1. 人們的第一想法往往是貪心(找最大/最小的堆合併),但是很容易找到貪心的反例:

      • 貪心:
        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
    2. 顯然利用貪心來做是錯誤的,貪心演算法在子過程中得出的解只是區域性最優,而不能保證使得全域性的值最優。
      如果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)壓縮編碼

  1. 問題分析:首先要清楚huffman編碼的原理,然後要在huffman的基礎上保證有序,所以合併過程從huffman的合併最小的兩個變成了合併相鄰兩個讓其總和最小,轉化成石子合併問題,每堆石子的數量相當於字母出現的頻率。
    時間複雜度:O(n^3),理論上可以得60分 (實際上,如果CCF的資料比較弱的話可以得滿分,親測2.437s過)。

  2. 時間優化:四邊形優化
    詳細證明:傳送門
    設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;
}