POI 1998 最輕的語言 The lightest language
從描述中看出,非前綴且權值和最小,於是我想到了哈夫曼編碼。類似的,對於這道題,我們可以考慮建立一個k叉樹,這個樹的每個葉節點代表一個字符串,只須找出最小的n個葉節點的權值,就是結果。
首先建立一個0節點,在此基礎上首先加入每個單個字母,當葉節點的個數不足n個時,取出最小的一個節點,對這個節點進行擴展,即把它的權值分別加上每個字母的權值,作爲新的k個葉節點。否則把前n小的節點取出,計算它們的權值之和。如果這個和小於當前已知最優解,更新最優解,否則結束,因爲再擴展一定不會比當前值小。
實際上我們並不需要建立樹結構,只需要對葉節點的權值抽象的維護。首先把每個字母的權值插入優先隊列,每次取出前n小的值計算,並以最小值來維護。
一般情況下我們用最小堆實現優先隊列,但是堆中可能會存在n^2個元素,大大超過了空間限制。但是優先隊列中只有前n小的元素是對我們有用的,我們可以以此改進堆,只存儲前n小的元素。當插入一個新的元素時,把堆中的最大值刪除,這樣維護的一定是前n小的。這樣又會帶來新的問題,在最小堆中取最大值是O(n)時間複雜度的。於是必須同時維護一個最大堆,使取刪最大值變爲O(logn)。需要同時維護兩個堆,在每個元素之間建立映射關係,每次刪除都要對兩個堆進行維護。
這樣雖然可以解決問題,但是會大大增加編程的複雜度,可以考慮不用堆來維護。我用了更強大的數據結構,平衡樹。平衡樹在插入、取最大、取最小、刪除上均有O(logn)的時間複雜度,使用平衡樹可以使編程思路更清晰。我用的是Treap。
以下是我用Treap實現的代碼。
/*
* Problem: POI1998 naj
* Author: Guo Jiabao
* Time: 2008.12.3 20:03:22
* State: Solved
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
using namespace std;
class tTreap
{
public:
class tNode
{
public:
int v,fix;
tNode *l,*r;
tNode(int tv,tNode *nl): v(tv),l(nl),r(nl)
{
fix=rand();
}
};
tNode *root,*null;
int sum,size,lim;
tTreap()
{
root=null=new tNode(0,0);
sum=0;
}
void rot_r(tNode *&p)
{
tNode *q=p->l;
p->l=q->r;
q->r=p;
p=q;
}
void rot_l(tNode *&p)
{
tNode *q=p->r;
p->r=q->l;
q->l=p;
p=q;
}
void _ins(tNode *&p,int v)
{
if (p==null)
{
p=new tNode(v,null);
}
else if (v <= p->v)
{
_ins(p->l,v);
if (p->l->fix < p->fix)
rot_r(p);
}
else
{
_ins(p->r,v);
if (p->r->fix < p->fix)
rot_l(p);
}
}
void ins(int v)
{
_ins(root,v);
size++;
sum+=v;
if (size>lim)
delmax();
}
int _delmin(tNode *&p)
{
if (p->l==null)
{
int v=p->v;
tNode *t=p;
p=p->r;
delete t;
return v;
}
else
{
return _delmin(p->l);
}
}
int _delmax(tNode *&p)
{
if (p->r==null)
{
int v=p->v;
tNode *t=p;
p=p->l;
delete t;
return v;
}
else
{
return _delmax(p->r);
}
}
int delmin()
{
size--;
int r=_delmin(root);
sum-=r;
return r;
}
int delmax()
{
size--;
int r=_delmax(root);
sum-=r;
return r;
}
};
const int MAXK=30;
const int INF=0x7FFFFFFF;
tTreap T;
int W[MAXK];
int N,K,Ans;
void init()
{
int i;
freopen("naj.in","r",stdin);
freopen("naj.out","w",stdout);
scanf("%d%d",&N,&K);
T.lim=N;
for (i=1;i<=K;i++)
{
scanf("%d",&W[i]);
T.ins(W[i]);
}
Ans=INF;
}
void solve()
{
int i,A;
while (T.sum<Ans)
{
if (T.size>=N)
{
if (T.sum<Ans)
Ans=T.sum;
else
break;
}
A=T.delmin();
for (i=1;i<=K;i++)
{
T.ins(A+W[i]);
}
}
}
int main()
{
init();
solve();
printf("%d",Ans);
return 0;
}
<h2><span class="mw-headline">最輕的語言</span></h2>
字母表AK由英文字母表的大寫字母K組成。一個被稱作重量的正整數被委派給字母表的每一個字母。一個由字母表AK的字母組成的字符串的重量是這個字 符串的所有字母的總重量。一個基於字母表AK的語言由這個字母表的字母組成的字符串的有限集合。一個語言的重量是所有它的字符串的重量之和。如果這個語言 的任意兩個字符串W、V,W不是V的前綴,那我們說這個語言不是前綴的。
我們想找出基於字母表AK的N個要素的非前綴語言的可能的最小重量。
例如
假設K=2,字母a的重量W(a)=2 字母b的重量W(b)=5。字符串ab的重量W(ab)=2+5=7。 W(aba)=2+5+2=9。語言J={ab,aba,b}的重量-W(J)=21。語言J不是非前綴,因爲字符串ab是aba的前綴。基於字母表A2最輕的三元非前綴語言(假設字母的重量如前)是{b,aa,ab};它的重量是16。
任務
寫一個程序:
- 從文本文件中讀取兩個整數n,k和字母表Ak 的k個字母的重量;
- 計算基於字母表Ak 的非前綴的n元語言的最小重量;
把結果寫到文本文件。
輸入
在輸入文件的首行有兩個被空格號分開的正整數n、k(2<=n<=10000, 2<=k<=26)。它們是語言中的字符串數和字母表中各個字母數。第二行包括被空格號隔開的k個正整數。每一個都不大於10000。第I個數是第I個字母的重量。
輸出
在輸出文件的首行應該寫一個整數-基於字母表Ak 的最輕的非前綴的n元語言重量。
樣例輸入
3 2 2 5
樣例輸出16