• [Noip2016]蚯蚓 (单调队列)


    题干

    本题中,我们将用符号[c]表示对c向下取整,例如:[3.0」= [3.1」=[3.9」=3。蛐蛐国最近蚯蚓成灾了!隔壁跳蚤国的跳蚤也拿蚯蚓们没办法,蛐蛐国王只好去请神刀手来帮他们消灭蚯蚓。蛐蛐国里现在共有n只蚯蚓(n为正整数)。每只蚯蚓拥有长度,我们设第i只蚯蚓的长度为a_i(i=1,2,...,n),并保证所有的长度都是非负整数(即:可能存在长度为0的蚯蚓)。每一秒,神刀手会在所有的蚯蚓中,准确地找到最长的那一只(如有多个则任选一个)将其切成两半。神刀手切开蚯蚓的位置由常数p(是满足0<p<1的有理数)决定,设这只蚯蚓长度为x,神刀手会将其切成两只长度分别为[px]和x-[px]的蚯蚓。特殊地,如果这两个数的其中一个等于0,则这个长度为0的蚯蚓也会被保留。此外,除了刚刚产生的两只新蚯蚓,其余蚯蚓的长度都会增加q(是一个非负整常数)。蛐蛐国王知道这样不是长久之计,因为蚯蚓不仅会越来越多,还会越来越长。蛐蛐国王决定求助于一位有着洪荒之力的神秘人物,但是救兵还需要m秒才能到来......(m为非负整数)蛐蛐国王希望知道这m秒内的战况。具体来说,他希望知道:m秒内,每一秒被切断的蚯蚓被切断前的长度(有m个数)m秒后,所有蚯蚓的长度(有n+m个数)。蛐蛐国王当然知道怎么做啦!但是他想考考你......

    Input

    第一行包含六个整数n,m,q,u,v,t,其中:n,m,q的意义见问题描述;

    u,v,t均为正整数;你需要自己计算p=u/v(保证0<u<v)t是输出参数,其含义将会在输出格式中解释。

    第二行包含n个非负整数,为ai,a2,...,an,即初始时n只蚯蚓的长度。

    同一行中相邻的两个数之间,恰好用一个空格隔开。

    保证1<=n<=10^5,0<m<7*10^6,0<u<v<10^9,0<=q<=200,1<t<71,0<ai<10^8。

    Output

    第一行输出[m/t]个整数,按时间顺序,依次输出第t秒,第2t秒,第3t秒……被切断蚯蚓(在被切断前)的长度。

    第二行输出[(n+m)/t]个整数,输出m秒后蚯蚓的长度;需要按从大到小的顺序依次输出排名第t,第2t,第3t……的长度。

    同一行中相邻的两个数之间,恰好用一个空格隔开。即使某一行没有任何数需要 输出,你也应输出一个空行。

    请阅读样例来更好地理解这个格式。

    Example

    in:

    3 7 1 1 3 1
    3 3 2

    out:

    3 4 4 4 5 5 6
    6 6 6 5 5 4 4 3 2 2

    又要开始扯淡了...

    完整的思路来源于这里:[膜拜大佬]: https://www.cnblogs.com/ljh2000-jump/p/6184271.html

    很不错的博客,包括基本的一步步的思路和解释都写出来了。唯一接受不能的是代码,没有注释还好说,为毛要把所有循环都写在一行里啊!(╯‵□′)╯︵┻━┻

    大家如果对部分分的实现有想法可以直接去看上面那篇博客,我这里直接着重介绍正解。(个人还是推荐大家去看一看的,毕竟真正考试的时候哪有这么容易想到正解╮(╯▽╰)╭)

    首先,每次都要取出一个最大值来进行操作,我们第一时间想到的就是堆,当然这么大的数据我们能手写单调队列还是手写而且上面的博客里也提到了这点,况且对这道题而言也多费不了多少事。然后就是还有一个给所有蚯蚓都加上q的操作,直接加到每一个蚯蚓身上显然时间上不允许,所以我们考虑开一个全局的tmp储存蚯蚓长度的总改变量,在堆里面只储存原始值(当然我们肯定是要更新队首元素的),每次更新时取出最大值,加上tmp,得到真实值,算出两个新元素值,tmp加上q,两个新元素值减去tmp,丢入堆中。

    那么我们最后为什么要用三个队列分别储存原始值、p×x和x-p×x呢?因为这三个队列各自具有单调性。

    对于单调性的研究,我们能得出这个队列一定是不上升的(蚯蚓长度可能为0,所以不是单调递减),我们可以考虑如下:(这里直接考虑q为自然数的情况,省去了先从q=0考虑开始的步骤)

    我们假设某一时刻队列中有一个最大值A,有一个值B,根据定义,B肯定是小于等于A的,此时A被切割成两个部分。然后经过了N轮切割,轮到B被切割了,我们再假设B被切割出来的部分比先前A切割出来的部分大,那么有:

    • A×p+N×q<(B+N×q)×p(只考虑乘以p的部分)
    • 那么展开一下得:A×p+N×q<B×p+N×q×p
    • 我们知道A>=B,而且p<1,那么显然上面的式子是矛盾的,则反证法可证出队列一定不上升

    其实我们换个角度来理解,我们一直都在对最大的那条切切切,虽然从整体上看我们切去的肯定不如增长的长度多,但是增长的长度那是针对所有蚯蚓而言的啊。每条蚯蚓都是在同时增长相同的值,那么说明他们之间的相对长度都是永远不变的(假如我们不去切他),那么我们现在去切蚯蚓,即使不会使长度更短,起码也不会更长吧。

    所以,根据上面的结论,我们分为三个单调队列来分别存储:原队列、p×x和x-p×x,这三个队列每单独一个拿出来都是保证不上升的。

    小提醒:

    由于题目中不是直接给出的p,而是通过给出分子分母要求我们自己计算p,来降低精度误差。在乘的时候需要开一个long long的临时变量,除完之后再转成int就可以了。其余的均可只开int。

    ps:写的还是太着急了,有空我再更新更新。。。

    代码:

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    #define ll long long
    const int maxn=7000000+10;
    const ll inf=1ll<<60;
    int m,n,q,t,tmp;
    int qu[3][maxn],head[3],tail[3];//维护三个单调队列
    ll u,v;
    bool Cmp(int a,int b){
        return a>b;
    }
    void Init(){
        scanf("%d%d%d%d%d%d",&n,&m,&q,&u,&v,&t);
        for(int i=1;i<=n;i++){
            scanf("%d",&qu[0][i]);
        }
        sort(qu[0]+1,qu[0]+1+n,Cmp);
        tmp=0;
        head[0]=head[1]=head[2]=1;
        tail[0]=n;//一开始所有蚯蚓都在第1队列里,我们把px都放入第2队列,x-px都放入第3队列
        tail[1]=tail[2]=0;
    }
    void Solve(){
        ll now;
        int from,l1,l2,out=0;
        bool flag=1;
        for(int i=1;i<=m;i++){
            now=-inf;//选取三个队列中的最大值
            from=-1;
            for(int j=0;j<=2;j++)
                if(head[j]<=tail[j]){
                    if(qu[j][head[j]]>now){//更新最大值
                        now=qu[j][head[j]];
                        from=j;
                    }
                }
            now+=tmp;
            out++;
            if(out==t){//保证在t的整数倍输出
                if(!flag) printf(" ");
                flag=0;
                printf("%lld",now);
                out=0;
            }
            l1=now*u/v;
            l2=now-l1;
            head[from]++;
            tmp+=q;
            l1-=tmp,l2-=tmp;
            qu[1][++tail[1]]=l1;//添加队列元素
            qu[2][++tail[2]]=l2;
            //printf("after %d s cut,tmp=%d
    ",i,tmp);
        }
        printf("
    "); 
    
        //以下为第二行输出
        m+=n;out=0;flag=true;
        for(int i=1;i<=m;i++) {
            now=-inf; from=-1; 
            for(int j=0;j<3;j++) 
                if(head[j]<=tail[j]){ 
                    if(qu[j][head[j]]>now){ 
                        now=qu[j][head[j]]; 
                        from=j; 
                    } 
                }
            now+=tmp; 
            out++; 
            if(out==t) { 
                if(!flag) printf(" "); 
                flag=false; 
                printf("%lld",now); 
                out=0; 
            }
            head[from]++;
        }
    }
    int main(){
        Init();
        Solve();
        return 0;
    }
    
  • 相关阅读:
    Java 传递参数时,传递一个变量快还是传递一个实体类?
    13 设计模式
    12 反射
    11.多线程&&并发
    10.输入输出
    9.异常Exception
    7.正则表达式
    5.数组
    6.常见对象
    上传本地项目到Github
  • 原文地址:https://www.cnblogs.com/Zfio/p/12716794.html
Copyright © 2020-2023  润新知