1. 程式人生 > >ccf 201612-4 壓縮編碼

ccf 201612-4 壓縮編碼

201612-4 試題名稱: 壓縮編碼 時間限制: 3.0s 記憶體限制: 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。

說明:這道題雖然是每個權值乘以深度,但是細細想來,要滿足字典序,類似哈夫曼樹那樣建樹即可,就將相鄰的節點生成一個新節點,每次合併一次,加一下l到r的和,就是l到r的節點形成了一棵樹的權值和,用求和代替乘積,但是和乘積是一樣的。 這樣問題就變成了動態規劃經典問題——取石子問題。 設dp[i][j]表示第i到第j堆石子合併的最優值,sum[i][j]表示第i到第j堆石子的總數量。那麼就有狀態轉移公式: 1、dp[i][j]=0 (i==j) 2、dp[i][j]=min(dp[i][k]+dp[k + 1][j])+sum[i][j] (i!=j) 此時演算法複雜為O(n^3)。 可以用平行四邊形優化降為O(n^2)。

要用平行四邊形優化則要證明w[i][j],dp[i][j]是否滿足四邊形不等式 w[a,c]+w[b,d]<=w[b,c]+wa,d 就稱其滿足凸四邊形不等式 或打表觀察w[i][j+1]-w[i][j]關於i的表示式,如果關於i遞減,則w滿足凸四邊形不等式 如果一個函式w[i][j],滿足 w[i’][j]<=w[i][j’] i<=i’<=j<=j’ 則稱w關於區間包含關係單調 如果w同時滿足四邊形不等式和區間單調關係,則dp也滿足四邊形不等式

通常的動態規劃的複雜度是O(n^3),四邊形不等式程式中跑一遍i只會跑一遍j,所以可以優化到O(n^2)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int N = 1005;

int a[N];
int dp[N][N];
int sum[N];

int main()
{
    int n;
    scanf("%d",&n);
    sum[0] = 0;
    memset(dp,inf,sizeof(dp));
    for(int i = 1;i <= n;++i)
    {
        scanf("%d",&a[i]);
        sum[i] = sum[i - 1] + a[i];
        dp[i][i] = 0;
    }
    for(int i = 1;i <= n;++i)
    {
        for(int j = 1;j + i <= n;++j)
        {
            if(dp[j][i + j] == inf){
                for(int k = j;k < i + j;++k)
                {
                    dp[j][i + j] = min(dp[j][i + j],dp[j][k] + dp[k + 1][i + j] + sum[i + j] - sum[j - 1]);
                }
            }
        }
    }
    printf("%d\n",dp[1][n]);
    return 0;
}

優化後31ms:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int N = 1005;

int a[N];
int dp[N][N];
int sum[N];
int s[N][N];

int main()
{
    int n;
    scanf("%d",&n);
    sum[0] = 0;
    memset(dp,inf,sizeof(dp));
    for(int i = 1;i <= n;++i)
    {
        scanf("%d",&a[i]);
        sum[i] = sum[i - 1] + a[i];
        dp[i][i] = 0;
        s[i][i] = i;
    }
    for(int i = 1;i <= n;++i)
    {
        for(int j = 1;j + i <= n;++j)
        {
            if(dp[j][i + j] == inf){
                for(int k = s[j][i + j - 1];k <= s[j + 1][i + j];++k)
                {
                    int x = dp[j][k] + dp[k + 1][i + j] + sum[i + j] - sum[j - 1];
                    if(dp[j][i + j] > x){
                        dp[j][i + j] = x;
                        s[j][i + j] = k;
                    }
                }
            }
        }
    }
    printf("%d\n",dp[1][n]);
    return 0;
}