NOIP 2016 蚯蚓 题解

本文探讨了一道关于蚯蚓切割的算法问题,通过优化从O(mlogn)到O(m)的时间复杂度,采用优先队列和巧妙的数据结构设计,解决了在每次操作中快速查询最大值和高效处理蚯蚓切割后的新增部分。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目传送门(洛谷)

       题目大意:有n条蚯蚓,每次把最长的切成两半,长度分别为⌊px⌋和x-⌊px⌋,其他的蚯蚓长度加q,重复m次,问某些时候切的蚯蚓的长度和最后的一些蚯蚓的长度。

       十分显然的,这道题我们可以用堆轻易实现,用堆来维护最长的蚯蚓,拿出来切成两半然后再塞回堆里面去。顺手再输出一下,这道题就ok了,于是我无脑的打出了以下代码:

#include <cstdio>
#include <cstring>
#include <cmath>
#define db double

int n,m,q,u,v,tt;
int t=0;
struct node{int x,y;};
node dui[8000010];
void swap(node &x,node &y){node pp=x;x=y;y=pp;}
int tot=0;
void up(int x)
{
    while(x>1&&dui[x].x+q*(tot-dui[x].y-1)>dui[x/2].x+q*(tot-dui[x/2].y-1))
    {
        swap(dui[x],dui[x/2]);
        x/=2;
    }
}
void down(int x)
{
    if(x>t/2)return;
    int ans=x;
    if(dui[ans].x+q*(tot-dui[ans].y-1)<dui[x*2].x+q*(tot-dui[x*2].y-1))ans=x*2;
    if(x*2+1<=t&&dui[ans].x+q*(tot-dui[ans].y-1)<dui[x*2+1].x+q*(tot-dui[x*2+1].y-1))ans=x*2+1;
    if(ans!=x)
    {
        swap(dui[x],dui[ans]);
        down(ans);
    }
}
void add(int x,int y)
{
    dui[++t].x=x;
    dui[t].y=y;
    up(t);
}
node pop()
{
    node re=dui[1];
    dui[1]=dui[t--];
    down(1);
    return re;
}

int main()
{
    scanf("%d %d %d %d %d %d",&n,&m,&q,&u,&v,&tt);
    db p=(db)u/(db)v;
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        add(x,0);
    }
    while(m--)
    {
        node x=pop();
        tot++;
        if(tot%tt==0)printf("%d ",x.x+q*(tot-x.y-1));
        add((int)floor((db)(x.x+q*(tot-x.y-1))*p),tot);
        add((int)((db)(x.x+q*(tot-x.y-1))-floor((db)(x.x+q*(tot-x.y-1))*p)),tot);
    }
    printf("\n");
    int total=0;
    tot++;
    while(t)
    {
    	total++;
    	node x=pop();
    	if(total%tt==0)printf("%d ",x.x+q*(tot-x.y-1));
    }
}

       一交上去,65分,顿时就懵逼了,翻翻讨论发现这题貌似需要卡常,于是又加上了读优以及一些硬核优化,于是。。变成了75分。。。

       伤感的提交记录:

       然后,就只能想想其他做法了。(正文开始)

       想一想刚刚的堆的时间复杂度,O(mlogn),看起来十分的优秀,仔细一想m是7*10^6,n是10^5,这个时间复杂度还是不怎么优的,于是,显然的,正解一定是O(m)的!

       那么一想到每一次需要做到O(1)查询最大值,毫无疑问一定是用优先队列了(不是堆),于是考虑将一开始的蚯蚓排序,每次取第一个(也就是最大的),把它切了塞回队列里面。

       相信聪明的你一定发现了问题——1、如何做到将其他的蚯蚓的长度+q  ;  2、塞回队列的时间是O(n)

       对于问题1,我们可以给每一只蚯蚓一个时间戳,记录他是什么时候进入队列的,那么每一次用   原来的长度 + (现在的时间-进队列的时间)*q  便可以求出它现在的长度,具体实现方法可以参考上面的堆代码。

       对于问题2,我们发现,不会优化。qaq

       于是发现把蚯蚓切成两半后是不能塞回队列的,那么,这个问题就转化成了:如何处置这两只蚯蚓?

       经过一番模拟后,发现每次被切出来的两只蚯蚓,其实长度是单调不上升的,比如我在时刻1切了一只蚯蚓,切成x1和x2,时刻2又切了一只蚯蚓,切成y1和y2,同时,x1+q,x2+q,然后一做对比,此时的x1\geqy1,并且x2\geqy2。为啥?这里给出一种可能比较通俗易懂的

     证明:

       我们设在有两只蚯蚓 f1,f2 分别在t1和t2时刻被切成两半。(t1<t2)

       因为我们每次切的都是最长的蚯蚓,所以在t1时刻,f1\geqf2

       我们把f1切成x1和x2后,在t1+1~t2这段时间,每次都是x1+q,x2+q,到了t2时刻的时候,x1变成了x1+(t2-t1-1)*q,x2变成了x2+(t2-t1-1)*q。

       为了简化这个式子以及方便理解,我们设t2~t1+1这段时间一共有tc个单位时间,也就是tc=t2-t1-1,以及将t2时刻的x1、x2和f2写为x1' , x2' , f2'

       于是式子变成了x1'=x1+tc*q,x2'=x2+tc*q

       以及f2'=f2+tc*q

       在t2时刻,我们将f2'切成了y1和y2,故y1=⌊p * f2'⌋=⌊p * (f2+tc*q)⌋ , y2=f2' - ⌊p * f2'⌋=(f2+tc*q) - ⌊p * (f2+tc*q)⌋

       顺便写出x1和x2——x1=⌊p * f1⌋ , x2=f1 - ⌊p * f1⌋

       将x1和x2带入x1'和x2'中,得到x1'=⌊p * f1⌋+tc*q,x2'=f1 - ⌊p * f1⌋+tc*q

       于是我们先比较一下x1'和y1

       将x1'和y1整理一下就变成了

       x1'=⌊p * f1⌋+tc*q=⌊p * f1+tc*q⌋

       y1=⌊p * (f2+tc*q)⌋=⌊p * f2+p*tc*q)⌋(下面为了方便用一下LaTeX

       \because f1 \geq f2 , 0 \leq q \leq 1

       \therefore f1*q \geq f2*q

       \because tc*q>0,0 \leq q \leq 1

       \therefore tc*q \geq tc*q*p

       \therefore x1' \geq y1

       比较x2'和y2的过程类似,就不细讲了(有兴趣的话可以自己手推一下),由此"x1\geqy1,并且x2\geqy2"

     得证

       那么这道题的思路就很明显了,开三个队列,对于第一个队列,存排好序的初始蚯蚓,第二个队列存被切出来的长度为⌊px⌋的蚯蚓,第三个队列存长度为x-⌊px⌋的蚯蚓,每次找出每条队列中长度最大的蚯蚓进行比较,得到在所有蚯蚓中长度最大的那只,拿出来切开再塞回第二第三条队列就好了。

       代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int n,m,q,u,v,t,tot=0;
double p;
struct node{
    int x,y;
    int kk(){return x!=0?(x+(tot-y-1)*q):0;}//计算当前长度,因为我调用kk的时候都是在当前时间蚯蚓的长度还没+q的时候,所以tot-y-1,还有如果x=0说明这个位置其实是没有蚯蚓的,所以要返回0
}a[100010],b[7000010],c[7000010];
int sta=1,stb=1,stc=1,&eda=n,edb=0,edc=0;
bool cmp(node x,node y){return x.x>y.x;}
void work(node *f,int &st)
{
    if(tot%t==0)printf("%d ",f[st].kk());//满足要求就输出
    b[++edb].x=(int)__builtin_floor((double)f[st].kk()*p);//__builtin_floor是向下取整,注意不能直接强行转成int
    b[edb].y=tot;//记录进队列时间
    c[++edc].x=f[st].kk()-(int)__builtin_floor((double)f[st].kk()*p);
    c[edc].y=tot;
    st++;
}

int main()
{
    scanf("%d %d %d %d %d %d",&n,&m,&q,&u,&v,&t);
    p=(double)u/(double)v;
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i].x),a[i].y=tot;
    sort(a+1,a+n+1,cmp);
    while(m--)
    {
        tot++;
        if(a[sta].kk()>=b[stb].kk()&&a[sta].kk()>=c[stc].kk())work(a,sta);//找出长度最长的蚯蚓
        else if(b[stb].kk()>=a[sta].kk()&&b[stb].kk()>=c[stc].kk())work(b,stb);
        else work(c,stc);
    }
    printf("\n");
    tot++;
    int total=0;
    while(sta<=eda||stb<=edb||stc<=edc)
    {
    	total++;//按长度依次弹出所有蚯蚓并输出
        if(a[sta].kk()>=b[stb].kk()&&a[sta].kk()>=c[stc].kk())(total%t==0?printf("%d ",a[sta++].kk()):sta++);
        else if(b[stb].kk()>=a[sta].kk()&&b[stb].kk()>=c[stc].kk())(total%t==0?printf("%d ",b[stb++].kk()):stb++);
        else (total%t==0?printf("%d ",c[stc++].kk()):stc++);
    }
}

       

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值