洛谷 P2827 蚯蚓(NOIp 提高组2016)

题目传送门

思路

首先想到的暴力,用一个优先队列,也就是大根堆来维护所有的蚯蚓。每次取出堆顶,把它分成两部分,然后插入堆中。同时用一个变量来维护从第一秒到当前时刻除了被切断的蚯蚓其它蚯蚓增长的总长度。由于不好对整个序列进行操作,我们可以让被切成两部分的蚯蚓减去每一秒增加的长度,即所有蚯蚓的相对大小还是没有变化,最后取出的时候加上这个变量即可复原真实的值。但是我们要进行\(m\)次操作,里面最后会有\(m+n\)个元素,总时间复杂度就是\(O(mlog_(m+n))\)。而这显然是不能\(AC\)这道题的。这复杂度已经可以过掉绝大部分测试点,也就是说只要想到被切断的蚯蚓减去每一秒的增加量 \(q\) 即为其它所有蚯蚓每一秒增加 \(q\) ,就可以得到绝大部分分。而正解需要推两个比较难以想到的式子才能得到,还要注意开\(long long\)要是考场上我就直接写暴力去了。。。

推导

设队首元素为 \(x\) ,计算切断成两段的系数为 \(p\) ,那么对于每一秒最长的那条蚯蚓,会得到两条新蚯蚓,长度分别为 \(⌊px⌋\)\(x - ⌊px⌋\) ,而假设现在有两个队列 \(q1\)\(q2\) ,直接乘上系数 \(p\) 的如 \(⌊px⌋\) 插入 \(q1\) ,而如 $ x - ⌊px⌋ $ 则插入队列 \(q2\) 。而剩余所有的蚯蚓长度都增加了 \(q\) 。设原本队列内第二个元素为 \(y\) ,这一秒过后长度变为 \(y + q\) ,被切断后分为 \(⌊p(y + q)⌋\)\(y + q - ⌊p(y + q)⌋\)两部分,然后分别插入两个队列 \(q1\)\(q2\) ,此时上一秒被切断的两部分也增加了 \(q\) ,即变为了 \(⌊px⌋ + q\)\(x - ⌊px⌋ + q\) ,现在分别比较两个队列中的元素。因为 \(q,x,y\) 都是是整数,所以将它们塞进向下取整中不会改变式子的值。把它塞进去之后就相当于比较 \(px + q\)\(py + pq\)\(x - px + q\)\(y - p(y + q) + q\)的大小关系。前一个式子很好判断,因为 \(x >= y\)\(0 < p < 1\) ,所以前者组成式子的两部分均大于等于后者,自然更大,即 \(q1\) 内的元素单调递减。将后两个式子同时减去 \(q\) 并且合并同类项得到 \(x(1-p)\)\(y(1-p) - pq\) 显然前者大于等于后者,等号成立当且仅当 \(x=y\) ,同时 \(q=0\) 。由证明可得出,得到的两个队列都是单调递减的,所以我们可以先扫一遍 \(m\) 秒,将所有的蚯蚓安排到三个队列中(可能有的蚯蚓始终没有被切断过,需要再单独搞一个队列,一开始将长度排序并存进去,维持单调性即可)。然后在分完之后将所有队列中队首的最大值弹出即可。这样的时间复杂度是\(O(m+(m+n))\),就可以切掉这个题了。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<cstdlib>
#include<ctime>
using namespace std;
typedef long long ll;
ll n,m,q,u,v,delta,l[8000005],t;
queue<ll> a;
queue<ll> b;
queue<ll> c;
ll cmp(ll a,ll b){
    return a>b;
}
ll get_max(){//此函数用来找出三个单调递减队列的最大值并取出
      ll x1,x2,x3;
      x1=a.empty()?-1000000000000:a.front();
      x2=b.empty()?-1000000000000:b.front();
      x3=c.empty()?-1000000000000:c.front();//若队列已为空那肯定不能取,由于是找最大值,赋一个极小值即可
      if(x1>=x2&&x1>=x3){
            a.pop();
            return x1;
      }
      else if(x2>=x1&&x2>=x3){
            b.pop();
            return x2;
      }
      else if(x3>=x1&&x3>=x2){
            c.pop();
            return x3;
      }
}
int main()
{
    scanf("%lld%lld%lld%lld%lld%lld",&n,&m,&q,&u,&v,&t);
    for(ll i=1; i<=n; i++){
        scanf("%lld",&l[i]);
    }
    sort(l+1,l+1+n,cmp);
    for(ll i=1; i<=n; i++){
        a.push(l[i]);
    }
    for(ll i=1; i<=m; i++){
        ll x=get_max()+delta;//因为要取出真实的值,所以要加上当前偏移量
        if(i%t==0){
              printf("%lld ",x);
	}
	b.push(x*u/v-delta-q);//被切断的减去q,相当于其余剩下的蚯蚓都加上q
        c.push(x-x*u/v-delta-q);
        delta+=q;//每一秒都要更新偏移量
    }
    printf("\n");
    for(ll i=1;i<=n+m;i++){
    	ll x=get_max()+delta;
    	if(i%t==0){
    	      printf("%lld ",x);
	}
    }
    return 0;
}

中间我还干了一件特别傻的事:一开始,我把在队列为空的情况下取出的值赋为-1000000,但中间是会出现比这个值小的情况的,就会出现队列已为空而我依然弹出队首的情况,于是就会\(RE\)。我调了一万年才发现这个问题。其中包括把数组开到1e8。。。

最后,不开longlong见祖宗

posted @ 2020-09-07 17:05  徐明拯  阅读(144)  评论(0编辑  收藏  举报