【洛谷】P2168荷马史诗

题解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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值