P2827 NOIP2016 蚯蚓
2023-07-18 20:21:54 # OI # Problem

题面

NOIP2016 提高组 蚯蚓 题面

题目描述

本题中,我们将用符号 c\lfloor c \rfloor 表示对 cc 向下取整,例如:3.0=3.1=3.9=3\lfloor 3.0 \rfloor = \lfloor 3.1 \rfloor = \lfloor 3.9 \rfloor = 3

蛐蛐国最近蚯蚓成灾了!隔壁跳蚤国的跳蚤也拿蚯蚓们没办法,蛐蛐国王只好去请神刀手来帮他们消灭蚯蚓。

蛐蛐国里现在共有 nn 只蚯蚓(nn 为正整数)。每只蚯蚓拥有长度,我们设第 ii 只蚯蚓的长度为 aia_i (i=1,2,,ni=1,2,\dots,n),并保证所有的长度都是非负整数(即:可能存在长度为 00 的蚯蚓)。

每一秒,神刀手会在所有的蚯蚓中,准确地找到最长的那一只(如有多个则任选一个)将其切成两半。神刀手切开蚯蚓的位置由常数 pp(是满足 0<p<10 < p < 1 的有理数)决定,设这只蚯蚓长度为 xx,神刀手会将其切成两只长度分别为 px\lfloor px \rfloorxpxx - \lfloor px \rfloor 的蚯蚓。特殊地,如果这两个数的其中一个等于 00,则这个长度为 00 的蚯蚓也会被保留。此外,除了刚刚产生的两只新蚯蚓,其余蚯蚓的长度都会增加 qq(是一个非负整常数)。

蛐蛐国王知道这样不是长久之计,因为蚯蚓不仅会越来越多,还会越来越长。蛐蛐国王决定求助于一位有着洪荒之力的神秘人物,但是救兵还需要 mm 秒才能到来……(mm 为非负整数)

蛐蛐国王希望知道这 mm 秒内的战况。具体来说,他希望知道:

  • mm 秒内,每一秒被切断的蚯蚓被切断前的长度(有 mm 个数);
  • mm 秒后,所有蚯蚓的长度(有 n+mn + m 个数)。

蛐蛐国王当然知道怎么做啦!但是他想考考你……

输入格式

第一行包含六个整数 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,,ana_1, a_2, \dots, a_n,即初始时 nn 只蚯蚓的长度。

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

保证 1n1051 \leq n \leq 10^50m7×1060 \leq m \leq 7 \times 10^60<u<v1090 < u < v \leq 10^90q2000 \leq q \leq 2001t711 \leq t \leq 710ai1080 \leq a_i \leq 10^8

输出格式

第一行输出 mt\left \lfloor \frac{m}{t} \right \rfloor 个整数,按时间顺序,依次输出第 tt 秒,第 2t2t 秒,第 3t3t 秒,……被切断蚯蚓(在被切断前)的长度。

第二行输出 n+mt\left \lfloor \frac{n+m}{t} \right \rfloor 个整数,输出 mm 秒后蚯蚓的长度;需要按从大到小的顺序,依次输出排名第 tt,第 2t2t,第 3t3t,……的长度。

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

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

样例 #1

样例输入 #1

3 7 1 1 3 1
3 3 2

样例输出 #1

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

样例 #2

样例输入 #2

3 7 1 1 3 2
3 3 2

样例输出 #2

4 4 5
6 5 4 3 2

样例 #3

样例输入 #3

3 7 1 1 3 9
3 3 2

样例输出 #3

//空行
2

提示

【样例解释1】

在神刀手到来前:33只蚯蚓的长度为3,3,23,3,2

11秒后:一只长度为33的蚯蚓被切成了两只长度分别为1122的蚯蚓,其余蚯蚓的长度增加了11。最终44只蚯蚓的长度分别为(1,2),4,3(1,2),4,3。括号表示这个位置刚刚有一只蚯蚓被切断

22秒后:一只长度为44的蚯蚓被切成了113355只蚯蚓的长度分别为:2,3,(1,3),42,3,(1,3),4

3秒后:一只长度为44的蚯蚓被切断。66只蚯蚓的长度分别为:3,4,2,4,(1,3)3,4,2,4,(1,3)

44秒后:一只长度为44的蚯蚓被切断。77只蚯蚓的长度分别为:4,(1,3),3,5,2,44,(1,3),3,5,2,4

55秒后:一只长度为55的蚯蚓被切断。88只蚯蚓的长度分别为:5,2,4,4,(1,4),3,55,2,4,4,(1,4),3,5

66秒后:一只长度为55的蚯蚓被切断。99只蚯蚓的长度分别为:(1,4),3,5,5,2,5,4,6(1,4),3,5,5,2,5,4,6

77秒后:一只长度为66的蚯蚓被切断。1010只蚯蚓的长度分别为:2,5,4,6,6,3,6,5,(2,4)2,5,4,6,6,3,6,5,(2,4)。所以,77秒内被切断的蚯蚓的长度依次为3,4,4,4,5,5,63,4,4,4,5,5,677秒后,所有蚯蚓长度从大到小排序为6,6,6,5,5,4,4,3,2,26,6,6,5,5,4,4,3,2,2

【样例解释2】

这个数据中只有t=2t=2与上个数据不同。只需在每行都改为每两个数输出一个数即可。

虽然第一行最后有一个66没有被输出,但是第二行仍然要重新从第二个数再开始输出。

【样例解释3】

这个数据中只有t=9t=9与上个数据不同。

注意第一行没有数要输出,但也要输出一个空行。

【数据范围】

解法

暴力

首先是最基础的暴力。

建立一个优先队列,每次挑出最长长度的蚯蚓,按照比例把它切成两份,然后放进优先队列里

那么其它蚯蚓的生长怎么处理呢?

总不能全部取出来每个都进行变化吧。

所以我们可以做一下转换:

其它蚯蚓都变长了那么其实就是没变长的两条蚯蚓变短了。

所以就可以用没变长的变短来体现这样的相对关系,并用一个数去记录它,方便我们由相对长度推出原长度,

作者因为注意到蚯蚓长度可能为0因此对每个蚯蚓长度都加1以保证所有蚯蚓长度1\ge1其实这步可有可无

每条要被切的蚯蚓在被切之前都需要输出一次(第一问),第二问的话就再枚举一遍就可以了。

注意处理相对长度时要先将要处理的蚯蚓长度变化为原长度处理,处理完后再变为相对长度,这是因为向下取整导致的精度误差。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,q,u,v,t,cnt=0;
int a[N];
priority_queue<int> Q;
int main()
{
    scanf("%d%d%d%d%d%d",&n,&m,&q,&u,&v,&t);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),a[i]++,Q.push(a[i]);//防止0不好处理
    for(int i=1,time=1;i<=m;i++,time++)
    {
        int w=Q.top();
        int a,b;
        Q.pop();
        int ew=w+cnt*q-1;
        if(time==t)
        {
            printf("%d ",ew);
            time=0;
        }
        a=floor(1.0*ew*u/v);//先变回原数
        b=ew-a;
        a++,b++;
        a-=cnt*q;
        b-=cnt*q;
        // cout<<w<<' '<<a<<' '<<b<<endl;
        Q.push(a-q);//其它蚯蚓变长了就相当于没变长的变短了
        Q.push(b-q);
        cnt++;//累计应该加的次数
    }
    puts("");//换行
    for(int i=1,time=1;i<=n+m;i++,time++)
    {
        if(time!=t) 
        {
            Q.pop();
            continue;
        }
        else
        {
            time=0;
            printf("%d ",Q.top()+m*q-1);
            Q.pop();
        }
        
    }
    return 0;
}

很不幸,这样只能获得90分。

优化

由于我们每次切都要放入队列两次,所以导致复杂度出了点问题。

而这时候有一条很重要的隐藏的性质:单调性。

我们发现先取出的蚯蚓被切成两份:长的和短的。

先切出的长的一定比后切出的长的要长,先切出的短的一定比后切出的要短,所以根本就不用每次都丢到一个优先队列里。

我们可以分两个普通队列来存切完后长的蚯蚓切完后短的蚯蚓

然后每次选蚯蚓就从原序列(排序后)的第一个和长蚯蚓队列中的第一个和短蚯蚓队列中的第一个中选一个,写几个分支比较即可。

然后处理完后,把它们一次性全部放到优先队列。

这样就可以最大化地利用优先队列

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,q,u,v,t,cnt=0;
queue<int> cut1,cut2,now;
int a[N];
inline int read()
{
	int x=0,y=1;//x是什么,y是正负 
	char c=getchar();
	while(c>'9'||c<'0')
	{
		if(c=='-') y=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9')
	{
		x=x*10+c-'0';
		c=getchar();
	}
	return x*y;
}
int get()
{
    int x1,x2,x3;
    x1=x2=x3=INT_MIN;
    if(!now.empty())x1=now.front();
	if(!cut1.empty())x2=cut1.front();
	if(!cut2.empty())x3=cut2.front();
	if(x1>=x2&&x1>=x3){now.pop();return x1;}
	else if(x2>=x1&&x2>=x3){cut1.pop();return x2;}
	cut2.pop();
    return x3;
}
priority_queue<int> Q;
int main()
{
    // scanf("%d%d%d%d%d%d",&n,&m,&q,&u,&v,&t);
    n=read();
    m=read();
    q=read();
    u=read();
    v=read();
    t=read();
    for(int i=1;i<=n;i++)
        a[i]=read(),a[i]++;//防止0不好处理
    sort(a+1,a+n+1);
    reverse(a+1,a+n+1);
    for(int i=1;i<=n;i++)
        now.push(a[i]);
    for(int i=1,time=1;i<=m;i++,time++)
    {
        int w,a,b;//开始求w,也就是该切哪个
        w=get();
        int ew=w+cnt*q-1;
        if(time==t)
        {
            printf("%d ",ew);
            time=0;
        }
        a=floor(1.0*ew*u/v);//先变回原数
        b=ew-a;
        // cout<<w<<' '<<a<<' '<<b<<endl;
        a++,b++;
        a-=cnt*q;
        b-=cnt*q;
        if(a<b) swap(a,b);
        cut1.push(a-q);
        cut2.push(b-q);
        // Q.push(a-q);//其它蚯蚓变长了就相当于没变长的变短了
        // Q.push(b-q);
        cnt++;//累计应该加的次数
    }
    puts("");//换行
    while(!now.empty()) Q.push(now.front()),now.pop();
    while(!cut1.empty()) Q.push(cut1.front()),cut1.pop();
    while(!cut2.empty()) Q.push(cut2.front()),cut2.pop();
    for(int i=1,time=1;i<=m+n;i++,time++)
    {
        if(time!=t)
        {
            Q.pop();
            continue;
        }
        time=0;
        printf("%d ",Q.top()+m*q-1);
        Q.pop();
    }
    return 0;
}

AC

image.png