JZOJ 6012 - 【NOIP2019模拟1.25A组】荷马史诗

题目地址

描述

考虑有 n n n个单词,每个单词出现次数为 w i w_i wi。考虑用一个 01 01 01串对其重新编号,使得最终满足 ∀ i , j ( i ≠ j ) \forall i,j(i\neq j) i,j(i=j),满足 s i s_i si不是 s j s_j sj的前缀。每个 01 01 01串的代价为 0 0 0的个数与两倍的 1 1 1的个数之和。

求满足条件的编号的最小代价。

n ≤ 1 0 3 n\leq 10^3 n103 w i ≤ 1 0 5 w_i\leq 10^5 wi105

分析

构建一棵 T r i e Trie Trie,每个叶子对应一个 01 01 01串,且这些串必定互不为彼此前缀。

令每个节点的左儿子为 0 0 0,边权为 1 1 1;每个右儿子为 1 1 1,边权为 2 2 2。定义一个点的深度为根到这个点路径上权值和。一个 01 01 01串的代价即为代表这个 01 01 01串的叶子的深度。

根据排序不等式可知,将按照深度排序的叶子与按频率排序的单词逆序配对,可使总代价最小。同时为了使答案更优,树上的每个节点要么是叶子,要么有两个儿子。

F l , a , b F_{l,a,b} Fl,a,b表示前 i i i层,已经确定不会再拓展的叶子节点有 l l l个, i − 1 i-1 i1层未确定的叶子结点有 a a a个, i i i层未确定的叶子结点有 b b b个。

分两种情况转移

  • 当前位于第 i i i层,将 i − 1 i-1 i1层中第一个节点变为叶子节点,则
    F l + 1 , a − 1 , b ← F l , a , b F_{l+1,a-1,b}\leftarrow F_{l,a,b} Fl+1,a1,bFl,a,b

  • 当前位于第 i i i层,将 i − 2 i-2 i2层未确定的节点全拓展出两个儿子,则通过边权为 1 1 1的边,到达第 i − 1 i-1 i1层的未确定的叶子节点有 a a a个,同理到达第 i i i层的未确定的叶子节点有 a a a个,则
    F l , a + b , a ← F l , a , b F_{l,a+b,a}\leftarrow F_{l,a,b} Fl,a+b,aFl,a,b

w i w_i wi从大到小排序,记 S u m n = ∑ i = 1 n w i \displaystyle Sum_n=\sum_{i=1}^n w_i Sumn=i=1nwi。每下降一层,即深度增加,则对于剩余未确定的叶子节点所代表的 01 01 01串来说,代价增加了 1 1 1,乘以这些串的个数,即为深度增加一,代价的增长值。而又因为要与按频率排序的单词逆序配对,故处理一个前缀和,那么增长值即为 S u m l + 1 Sum_{l+1} Suml+1

发现这个 l l l可以滚动,于是滚动一下就好了。上述的 i i i在转移时无关紧要,故不位于状态中。

时间复杂度 O ( n 3 ) O(n^3) O(n3)

代码

uses math;
var
        n,i,j,k,now,nowx:longint;
        a:array[0..1001] of longint;
        f:array[0..1,-1..1000,0..1000] of longint;
procedure qsort(l,r:longint);
var
        i,j,t,mid:longint;
begin
        i:=l;
        j:=r;
        mid:=a[(l+r) shr 1];

        while i<j do
        begin
                while a[i]>mid do inc(i);
                while a[j]<mid do dec(j);

                if i<=j then
                begin
                        t:=a[i]; a[i]:=a[j]; a[J]:=t;

                        inc(i); dec(j);
                end;
        end;

        if i<r then qsort(i,r); if l<j then qsort(l,j);
end;
begin
        readln(n);

        for i:=1 to n do read(a[i]);

        qsort(1,n); for i:=n downto 1 do inc(a[i],a[i+1]);

        fillchar(f,sizeof(f),120); f[0,1,0]:=0;

        for i:=0 to n-1 do
        begin
                now:=now xor 1; nowx:=now xor 1;

                for j:=0 to n-i do
                        for k:=0 to n-i-j do
                                if f[nowx,j,k]<>2021161080 then
                                begin
                                        f[now,j-1,k]:=min(f[now,j-1,k],f[nowx,j,k]);
                                        f[nowx,j+k,j]:=min(f[nowx,j+k,j],f[nowx,j,k]+a[i+1]);
                                        f[now,j,k]:=2021161080;
                                end;
        end;

        writeln(f[now,0,0]);
end.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值