【题解】「NOIP2016」蚯蚓

题目大意:

一共有n只蚯蚓,每只蚯蚓有一个长度,并且蚯蚓会按每年q厘米的速度增长。现在每年选择一只最长的蚯蚓,将其按p的比例切成两半,被切的的蚯蚓这一年不能增长,问每年被切的蚯蚓的长度和m年后所有蚯蚓的长度(据题意只需输出部分结果)

题目分析:

看到这题首先想到的是使用堆来做(因为,切蚯蚓符合堆的查询极值、插入和删除)。但考虑到数据的范围( 1 < = n < = 1 0 5 1<=n<=10^5 1<=n<=105, 0 < = m < = 7 ∗ 1 0 6 0<=m<=7*10^6 0<=m<=7106),由于使用堆的时间复杂度为O(m*logn),其结果必然会导致超时,所以只有另辟蹊径。

再仔细观察,发现每年切最长的一只蚯蚓,说明切过的蚯蚓长度具有单调性,即先切的蚯蚓的左段一定大于等于后切蚯蚓的左段,先切蚯蚓的右段一定大于等于后切蚯蚓的右段。

以下为证明过程:为方便,设两只蚯蚓长度为a,b,切后的长度分别是a1,a2,b1,b2.

  • a>=b
  • 所以 ap>=bp,a-ap>=b-bp
  • 因为切这两只蚯蚓的时候这两只蚯蚓都少增长了q厘米
  • 所以可证a1>=b1,a2>=b2
    因此我们可以想到这题可以使用普通队列来做,使复杂度降至O(m)
  • 使用3个队列分别保存没被切过的蚯蚓,被切过的蚯蚓左段,和被切过的蚯蚓右段
    注意因为最开始给的蚯蚓数据并不是按照递减顺序,所以可以选择提前排序或者第一个队列使用优先队列
  • 每次取出3个队列中最长的一只蚯蚓,将其切断都分别放入后两个数组,依此类推循环m次即可求出结果
  • 还需要考虑蚯蚓的增长长度问题,由于每次除了被切的蚯蚓,其他所有的蚯蚓都在增长,所以只需要再从队列中取元素的时候加上(i-1)q,放回时减去iq的长度(因为被切的蚯蚓这年不增长,所以多减q厘米,具体实现参见程序)
#include<bits/stdc++.h> 
using namespace std;

const int inf=-0x3f3f3f3f;
int n,m,q,u,v,t,in[100005],delta;
double p;
queue<int> a,b,c;

void read() {
	scanf("%d%d%d%d%d%d",&n,&m,&q,&u,&v,&t);
	p=u*1.0/v;
	for(int i=1;i<=n;i++) {
		scanf("%d",&in[i]);
	}
	sort(in+1,in+1+n);
	for(int i=n;i>=1;i--) {
		a.push(in[i]);
	}	
} 
void work() {
	for(int i=1;i<=m;i++) {
		int x=inf,y,z,out;
		if(a.size()) x=a.front();
		if(i==1) {
			a.pop();
			x+=delta;
			out=x;
			b.push(int(x*p)-delta-q);c.push(x-int(x*p)-delta-q);
		}
		else {
			y=b.front(),z=c.front();
			if(x>=y&&x>=z) {
				a.pop();
				x+=delta;
				out=x;
				b.push(int(x*p)-delta-q);c.push(x-int(x*p)-delta-q);
			}
			else if(y>=x&&y>=z) {
				b.pop();
				y+=delta;
				out=y;
				b.push(int(y*p)-delta-q);c.push(y-int(y*p)-delta-q);
			}
			else {
				c.pop();
				z+=delta;
				out=z;
				b.push(int(z*p)-delta-q);c.push(z-int(z*p)-delta-q);
			}
		}
		if(i%t==0) printf("%d ",out);
		delta+=q;
	}	
}
void print() {
	for(int i=1;i<=n+m;i++) {
		int x=inf,y=inf,z=inf,out;
		if(a.size()) x=a.front();
		if(b.size()) y=b.front();
		if(c.size()) z=c.front();
		if(x>=y&&x>=z) {
			out=x;
			a.pop();
		}
		else if(y>=x&&y>=z) {
			out=y;
			b.pop();
		}
		else {
			out=z;
			c.pop();
		}
		if(i%t==0) printf("%d ",out+delta);
	}	
}
int main() {
	read();
	work();
	printf("\n");
	print();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值