UOJ 264 NOIP2016 DAY2 T2 浅谈队列单调性及辅助队列时间戳

来源:互联网 发布:mac air 13寸 长宽 编辑:程序博客网 时间:2024/06/14 06:00

这里写图片描述
世界真的很大
可能是NOIP临近了吧,感觉这几天都在刷NOIP的原题呢
基本上已经记不得去年我是以什么样的心态在做这道题了233
当时就直接暴力模拟

看题先:

description

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

input

从标准输入读入数据。第一行包含六个整数 n,m,q,u,v,tn,m,q,u,v,t,其中:n,m,qn,m,q 的意义见【问题描述】;u,v,tu,v,t 均为正整数;你需要自己计算 p=u/vp=u/v(保证 0<u<v0<u<v);tt 是输出参数,其含义将会在【输出格式】中解释。第二行包含 nn 个非负整数,为 a1,a2,…,ana1,a2,…,an,即初始时 nn 只蚯蚓的长度。同一行中相邻的两个数之间,恰好用一个空格隔开。保证 1n1051n1050m7×1060m7×1060<u<v1090<u<v1090q2000q2001t711t710ai1080ai108

output

输出到标准输出。第一行输出 ⌊mt⌋⌊mt⌋ 个整数,按时间顺序,依次输出第 tt 秒,第 2t2t 秒,第 3t3t 秒,……被切断蚯蚓(在被切断前)的长度。第二行输出 ⌊n+mt⌋⌊n+mt⌋ 个整数,输出 mm 秒后蚯蚓的长度;需要按从大到小的顺序,依次输出排名第 tt,第 2t2t,第 3t3t,……的长度。同一行中相邻的两个数之间,恰好用一个空格隔开。即使某一行没有任何数需要输出,你也应输出一个空行。请阅读样例来更好地理解这个格式。

看一下题。。根本不是什么算法题了,就是暴力模拟
时间基本上会花在每次选最长的蚯蚓那里,这个肯定是要数据结构来维护了
看一下范围,我们需要模拟m次,m是7*10^6的级别,nlogn都很悬
NOIP不可能卡常,所以这道题就肯定是O(n)的,线段树什么的直接否决掉吧

去看一下数据,看能不能分治,有60分的q为0,就是说根本不会增加,考虑到每次取都是取最大值,而且还要随时往里面加蚯蚓,一下子就想到堆,用堆来维护一下,大概有60分。。但是堆自带一个log,实际情况下就没有60分了,只有50分
堆50分代码:

#include<stdio.h>#include<algorithm>#include<queue>#include<cstring>#include<vector>#include<iostream>using namespace std;typedef long long dnt;priority_queue <dnt> q1;vector <dnt> va;int n,m,q,u,v,t;dnt a[500010];int main(){    scanf("%d%d%d%d%d%d",&n,&m,&q,&u,&v,&t);    for(int i=1;i<=n;i++)        cin >> a[i];    sort(a+1,a+n+1);    for(int i=0;i<n;i++)        q1.push(a[n-i]);    for(int i=1;i<=m;i++)    {        dnt x=q1.top();        q1.pop();        if(i%t==0) cout << x << " " ;        dnt tmp1=x*u/v;        dnt tmp2=x-tmp1;        q1.push(tmp1),q1.push(tmp2);    }    printf("\n");    int tmp=q1.size();    for(int i=1;i<=tmp;i++)    {        dnt xx=q1.top();        q1.pop();        if(i%t==0) cout << xx << " ";    }    return 0;}

然后考虑堆的时间复杂度主要是花在一个一个吧元素取出来的时候,是nlogn的。
考虑能不能优化这一步,至少要优化到n
考虑能O(n)读取的单调数据结构,还有单调队列和单调栈

分析一下题目性质,每一次取出的蚯蚓,后续取出的都会比他小,其切开以后也会比自己本身小,好像有单调关系,是先取出,后放入,所以单调栈就不行了,得用单调队列

单调队列的话,由于每只蚯蚓都有可能被切,所以队尾不能弹走,但是这样一来就无法保证单调队列的单调性了
考虑一共有三种蚯蚓,初始的,砍了以后的前一半,砍了以后的后一半,而对于每种蚯蚓之间,选择的顺序一定是和长度单调的。
所以我们就可以建三个队列q1,q2,q3
q1里面装着一开始的蚯蚓,每次从三个队列里面选一个最大的切开,前一半扔到q2里面,后一半扔到q3里面
由于是3个单调队列,最后合并就是O(n)的了
这样q为0的60分就到手了
单调队列60分代码:

#include<stdio.h>#include<algorithm>#include<queue>#include<cstring>#include<vector>#include<iostream>using namespace std;typedef long long dnt;queue <dnt> q1,q2,q3;vector <dnt> va;int n,m,q,u,v,t;dnt a[500010];bool cmp(const dnt &a,const dnt &b){    return a>b;}dnt get(){    dnt rt=0;    if(!q1.empty()) rt=max(q1.front(),rt);    if(!q2.empty()) rt=max(q2.front(),rt);    if(!q3.empty()) rt=max(q3.front(),rt);    if(!q1.empty() && q1.front()==rt)    {        q1.pop();        return rt;    }     if(!q2.empty() && q2.front()==rt)    {        q2.pop();        return rt;    }     if(!q3.empty() && q3.front()==rt) q3.pop();    return rt;}int main(){    scanf("%d%d%d%d%d%d",&n,&m,&q,&u,&v,&t);    for(int i=1;i<=n;i++)        cin >> a[i];    sort(a+1,a+n+1);    for(int i=0;i<n;i++)        q1.push(a[n-i]);    for(int i=1;i<=m;i++)    {        dnt x=get();        if(i%t==0) cout << x << " " ;        dnt tmp1=x*u/v;        dnt tmp2=x-tmp1;        q2.push(tmp1),q3.push(tmp2);    }    printf("\n");    int tmp=q1.size()+q2.size()+q3.size();    for(int i=1;i<=tmp;i++)    {        dnt xx=get();        if(i%t==0) cout << xx << " ";    }    return 0;}

现在考虑加上q的情况
其实如果不是被拿出去切得那一只蚯蚓,其余的蚯蚓的长度全部会增加q,所以单调性不会变化
而我们想要知道一只蚯蚓有多长,当且仅当我们想要切这只蚯蚓的时候。所以我们只是需要这只倒霉的蚯蚓上一次被切的时间和现在正要被切的时间之差,就能知道其长长了多少个q
所以我们想要给每一只蚯蚓打上时间戳。
但是考虑到蚯蚓的数量和编号并不固定,所以数组时间戳的实现难度较大
考虑每一只蚯蚓都会存在于一个队列里面,而我们只需要找一个与其队列结构完全一样的时间戳队列就能实现与蚯蚓之间的一一对应了,AC

完整代码:

#include<stdio.h>#include<algorithm>#include<queue>#include<cstring>#include<vector>#include<iostream>using namespace std;typedef long long dnt;queue <dnt> q1,q2,q3,m1,m2,m3;vector <dnt> va;int n,m,q,u,v,t;dnt a[500010];bool cmp(const dnt &a,const dnt &b){    return a>b;}dnt get(int tim){    dnt rt=0;    if(!q1.empty()) rt=max(q1.front()+q*(tim-m1.front()),rt);    if(!q2.empty()) rt=max(q2.front()+q*(tim-m2.front()),rt);    if(!q3.empty()) rt=max(q3.front()+q*(tim-m3.front()),rt);    if(!q1.empty() && q1.front()+q*(tim-m1.front())==rt)    {        q1.pop(),m1.pop();        return rt;    }     if(!q2.empty() && q2.front()+q*(tim-m2.front())==rt)    {        q2.pop(),m2.pop();        return rt;    }     if(!q3.empty() && q3.front()+q*(tim-m3.front())==rt) q3.pop(),m3.pop();    return rt;}int main(){    scanf("%d%d%d%d%d%d",&n,&m,&q,&u,&v,&t);    for(int i=1;i<=n;i++)        cin >> a[i];    sort(a+1,a+n+1);    for(int i=0;i<n;i++)        q1.push(a[n-i]),m1.push(0);    for(int i=1;i<=m;i++)    {        dnt x=get(i-1);        if(i%t==0) cout << x << " " ;        dnt tmp1=x*u/v;        dnt tmp2=x-tmp1;        q2.push(tmp1),m2.push(i);        q3.push(tmp2),m3.push(i);    }    printf("\n");    int tmp=q1.size()+q2.size()+q3.size();    for(int i=1;i<=tmp;i++)    {        dnt xx=get(m);        if(i%t==0) cout << xx << " ";    }    return 0;}/*EL PSY CONGROO*/
原创粉丝点击