[NOIP2016 提高组] 蚯蚓 (基础数据结构 单调队列)

NOIP2016 提高组] 蚯蚓 (基础数据结构 单调队列)

洛谷链接

Description:

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

蛐蛐国里现在共有 n n n 只蚯蚓( n n n 为正整数)。每只蚯蚓拥有长度,我们设第 i i i 只蚯蚓的长度为 a i a_i ai

( i = 1 , 2 , … , n i=1,2,\dots ,n i=1,2,,n),并保证所有的长度都是非负整数(即:可能存在长度为 0 0 0 的蚯蚓)。

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

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

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

m m m 秒内,每一秒被切断的蚯蚓被切断前的长度(有 m m m 个数);
m m m 秒后,所有蚯蚓的长度(有 n + m n + m n+m 个数)。
蛐蛐国王当然知道怎么做啦!但是他想考考你……

思路:

观察到每次需取出最大值后又将分出来的两只蚯蚓加入回去,很容易想到使用队列(优先队列)。

那么如何解决每次取出后所有没有被切的蚯蚓长度增加 q 呢?

每次让所有其他蚯蚓长度增加对排序产生的影响,其实相当于让当前蚯蚓减小产生的影响。

(如 1,2,3,4 中,让 4 减小 3 和让 1,2,3 都增加 3 这两种操作对排序产生的影响是一样的)

注意,虽然对排序影响一样,但是我们切的时候需要用到的是蚯蚓原长度,队列里面存的是 原长度减去了一个值之后的相对长度,因此我们每次拿出队列中的元素之后应先转换为原长,再进行切割。

转换公式(手推也不复杂):

原长度 = 取出值 + (当前秒数 - 1)* q;

再次注意,切割后我们获得的长度也是蚯蚓的原长,而队列里的元素是相对长度,因此需要再将 原长转换为相对长度 再放回队列。公式其实就是上述公式反过来,但是因为切完后所有蚯蚓都增长了 q ,所以此时还要多减一个 q 。

公式:

放入值 = 原长度 - 当前秒数 * q;

解决长度问题,现在只需要想如何维护。

朴素 (TLE) 思路:

使用优先队列,每次取出最大值,切完放回去,最后输出。

观察到数据范围和优先队列的复杂度 O ( m ∗ log ⁡ 2 n ) O( m * \log_2n) O(mlog2n) 使用优先队列会超时 (本人就是个没注意到的憨憨) 。因此无法使用优先队列。

正解:

仔细观察 (这谁能看出来啊) 被切的蚯蚓的左右长度,我们能发现,先切蚯蚓的左部分 > 后切蚯蚓的左部分,右部分同理;

证明:

假设
先切蚯蚓总长为 a 0 a_0 a0 ,左右部分为 a 1 , a 2 a_1,a_2 a1,a2
后切蚯蚓总长 b 0 b_0 b0 ,左右部分为 b 1 , b 2 b_1,b_2 b1,b2

① 这两条蚯蚓都有一次没有增长 q ,因此相当于长度不变;
② 由题意: a 1 = a 0 × q , a 2 = a 0 − a 1 a_1=a_0 \times q, a_2=a_0-a_1 a1=a0×q,a2=a0a1 b 0 , b 1 , b 2 b_0,b_1,b_2 b0,b1,b2同理;
a 0 ≥ b 0 ⇒ a 0 × q ≥ b 0 × q ⇒ a 1 ≥ b 1 ⇒ a 2 ≥ b 2 a_0 \geq b_0 \Rightarrow a_0 \times q \geq b_0 \times q \Rightarrow a_1 \geq b_1 \Rightarrow a_2 \geq b_2 a0b0a0×qb0×qa1b1a2b2

这时我们发现,除了第一次将全体元素放入队列时可能无序外,之后切出来的两段一定是有序的。

因此,我们可以使用三个普通队列而不使用优先队列,que[0] 存初始值(已经排好序的),que[1] 和 que[2] 存切出来的左部分和右部分,每次取元素只需取出三个队头元素中最大的那个,输出时也是一样。

完整代码:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
#include<vector>
#include<math.h>
#include<queue>
using namespace std;
typedef long long ll;
typedef double dd;
typedef pair<int, int> pii;
typedef pair<dd, dd> pdd;
const int MAXN = 100010;
const ll inf = 1e11 + 7;
const int MAXM = 8000010;

ll n, m, q, u, v, t;

queue<ll> que[3];
ll a[MAXM];//用来存初始数据和每次切割的蚯蚓长度的数组

bool cp(ll a, ll b)//大的排前面
{
	return a > b;
}

int get_max()//找出队头元素最大的队列的编号并返回
{
	ll mx = -inf;
	int h = 0;
	if (que[0].size() && que[0].front() > mx) h = 0, mx = que[0].front();
	if (que[1].size() && que[1].front() > mx) h = 1, mx = que[1].front();
	if (que[2].size() && que[2].front() > mx) h = 2, mx = que[2].front();
	return h;
}

int main()
{
	scanf("%lld%lld%lld%lld%lld%lld", &n, &m, &q, &u, &v, &t);
	for (int i = 1;i <= n;i++) scanf("%lld", &a[i]);
	sort(a + 1, a + 1 + n, cp);
	for (int i = 1;i <= n;i++) que[0].push(a[i]);//进行完这个for之后a[]数组内部元素就全部到que[0]中了,所以可以继续用来存每次切割的蚯蚓长度
	for (int i = 1;i <= m;i++)
	{
		int h = get_max();
		ll len = que[h].front();
		que[h].pop();
		len = len + ((ll)i - 1ll) * q;//相对长度还原为原长度
		a[i] = len;
		ll x = u * len / v;//左部分原长
		ll y = len - x;//右部分原长
		x = x - (ll)i * q;//左部分相对长度
		y = y - (ll)i * q;//右部分相对长度
		que[1].push(x);
		que[2].push(y);
	}
	for (int i = 1;i * t <= m;i++)
	{
		if (i == 1) printf("%lld", a[i * t]);
		else printf(" %lld", a[i * t]);
	}
	printf("\n");
	int cnt = 1;
	for (int i = 1;i * t <= n + m;i++)
	{
		while (cnt < i * t)
		{
			cnt++;
			int h = get_max();
			que[h].pop();
		}
		int h = get_max();
		if (i == 1) printf("%lld", que[h].front() + m * q);//还原为原长度后输出
		else printf(" %lld", que[h].front() + m * q);
	}
	printf("\n");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值