P2168
[NOI2015] 荷马史诗
重载运算符不要写反。。。
然后这题是构造一个
-
将
n 个词汇的出现次数视为该点权值,加入小根堆。 -
加入权值为 0 的节点,直至节点个数
n 满足(n-1)\mod (k-1)=0 。 -
每次从小根堆中取出
k 个,将它们的权值合并,设此点为p ,则p 的权值为k 个点的权值之和。 -
将
p 加入小根堆。 -
重复上述 3 和 4 两步,直至堆的大小为 1 。
关于为什么要有第二步,可以以样例 2 为例,如果在去掉第二步的情况下做样例 2 ,就会发现有一个权值为 9 的节点是在倒数第二次合并中合并的,而最优的方法应该把这个节点在倒数第一次与另一个权值为 9 的节点以及先前的节点
出现这种问题的实质就是我们让一层内的合并不足
所以加入了权值为 0 的节点,就让合并不足
这个贪心的最优大概就是自证不难了,毕竟让权值最大的节点离根节点的距离最小了,应该弄几个不等式就可以证了罢。。。
大概就是那个如果
同时注意这里的合并要使字符串长度最大者最小,那么我们还要将节点赋上深度的,在权值相同时,深度小(合并次数少)的优先。然而这个贪心也比较玄学,可以感性的作一个微扰的证明,假设我们让深度大的(假设为
为了维护这个深度和权值之间的优先级,我们可以使用一个结构体。然后就是重载运算符之类的事情了。。。
时间复杂度
代码:
#include<iostream>
#include<cstdio>
#include<queue>
#define ll long long
using namespace std;
struct node{
ll w,h;
bool operator <(const node&rhs) const {
if(w==rhs.w) return h>rhs.h;
return w>rhs.w;
}
}t;
ll n,k,w,p,ans,h;
priority_queue<node> q;
inline ll read() {
ll ret=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-f;ch=getchar();}
while(ch>='0'&&ch<='9') {ret=(ret<<3)+(ret<<1)+ch-'0';ch=getchar();}
return ret*f;
}
void write(ll x) {
if(x<0) {x=-x;putchar('-');}
ll y=10,len=1;
while(y<=x) {y*=10;len++;}
while(len--) {y/=10;putchar(x/y+48);x%=y;}
}
int main() {
n=read();k=read();
for(ll i=1;i<=n;i++) {
w=read();t.w=w;t.h=1;
q.push(t);
}
while((q.size()-1)%(k-1)!=0) {
t.w=0;t.h=1;q.push(t);
}
while(q.size()>=k) {
p=0;h=-1;
for(ll i=1;i<=k;i++) {
p+=q.top().w;h=max(q.top().h,h);
q.pop();
}
ans+=p;t.w=p;t.h=h+1;q.push(t);
}
write(ans);putchar('\n');
write(q.top().h-1);
return 0;
}