题解by:落寞音箫
看到很多大佬都用优先队列啊,堆啊什么的,但是我有一个O(N)的算法。。。有一些局限的双队列。。。。
- 其实ylsoi[https://www.luogu.org/space/show?uid=20059]大佬早在7月就在合并果子里发了这种题的最优做法——双队列,但是似乎并没有引起什么关注,所以我现在看到这道题,就又想来发一发,介绍一下这种算法。
- 我们来看看这个算法:
For(i,1,top){
c=0,maxx=0;
For(j,1,k){
if((a[nua].w<b[nub].w||a[nua].w==b[nub].w&&a[nua].h<=b[nub].h)&&a[nua].w!=-1||b[nub].w==-1){
sum+=a[nua].w;
maxx=Max(maxx,a[nua].h);
c+=a[nua++].w;
}
else if((a[nua].w>b[nub].w||a[nua].w==b[nub].w&&a[nua].h>b[nub].h)&&b[nub].w!=-1||a[nua].w==-1){
sum+=b[nub].w;
maxx=Max(maxx,b[nub].h);
c+=b[nub++].w;
}
}
b[++note].w=c;
b[note].h=maxx+1;
}
在这个代码块中,我用nua来存原结构体a进行到哪里了,用nub来存合成的数的结构体b进行到的位置。从1开始,进行top次(即Huffman树里要合并多少次),这样我们就可以跳出模拟题意的桎梏,通过这种方式保证找到最优解。
那么为什么是这样呢?
既然我们已经将a排过序了,那么是不是a是从大到小,从高价值到低价值以此排列的?既然如此,且合成的b是由a从前到后依次加上来的,那么b是不是也是递增的?解决了b为什么是单调递增的了吗?
那么a递增,b递增,为什么就可以用O(n)的时间合并呢?
在代码块中,只要a【nua】价值大于等于b【nub】或者没有b【nub】了,我们就加入它;只要a【nua】价值小于等于b【nub】或者没有a【nua】了,我们就加入它。
加完一轮后再加到b结构体里面。这样是不是就可以既保持跑的最优,又可以保证b始终单调递增。
代码如下:(请务必观察前面的宏定义!)
//这是代码
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define re register
#define ll long long
#define For(i,a,b) for(re ll i=a;i<=b;++i)
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
ll n,k,top;
ll nua=1,nub=1,sum;
struct node{
ll w,h;//h计算已经加了多少次,其实a结构体始终是1
}a[MAXN],b[MAXN];//原结构体数组与合成的结构体数组
int cmp(node aa,node bb){
if(aa.w<bb.w)return 1;
if(aa.h<bb.h)return 1;
return 0;
}//处理sort;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;ch<48||ch>57;ch=getchar())if(ch=='-')f=-f;
for(;ch>=48&&ch<=57;x=(x<<1)+(x<<3)+(ch^48),ch=getchar());
return x*f;
}
int main(){
ll note=0;//note用于计算已经合成的结构体
n=read(),k=read();
For(i,1,n)a[i].w=read(),a[i].h=1;
top=(n-1)%(k-1);//用来检测是否需要加入虚拟节点
if(top)top=(k-1)-top;//如果要加的话,加多少
For(i,1,top)a[++n].w=0,a[n].h=1;//加入节点
sort(a+1,a+1+n,cmp);//排序
a[n+1].w=-1;//把a结构体的最后一个标记为不能继续加
For(i,1,n+1)b[i].w=-1;//先把b结构体都标记一下
ll c=0,maxx=0;
top=(n-1)/(k-1);//需要加入的次数
For(i,1,top){
c=0,maxx=0;//每次记录一下
For(j,1,k){
if((a[nua].w<b[nub].w||a[nua].w==b[nub].w&&a[nua].h<=b[nub].h)&&a[nua].w!=-1||b[nub].w==-1){
sum+=a[nua].w; maxx=Max(maxx,a[nua].h);//找加的次数最多的节点
c+=a[nua++].w;
}
else if((a[nua].w>b[nub].w||a[nua].w==b[nub].w&&a[nua].h>b[nub].h)&&b[nub].w!=-1||a[nua].w==-1){//将上一个再判断一次
sum+=b[nub].w;
maxx=Max(maxx,b[nub].h);
c+=b[nub++].w;
}
}
b[++note].w=c;//合成的节点
b[note].h=maxx+1;//已经又加过这一次了。
}
printf("%lld\n%lld",sum,b[note].h-1);//输出加过的sum与最多加过的次数。
return 0;
}