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 x−⌊px⌋ 的蚯蚓。特殊地,如果这两个数的其中一个等于 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(m∗log2n) 使用优先队列会超时 (本人就是个没注意到的憨憨) 。因此无法使用优先队列。
正解:
仔细观察 (这谁能看出来啊) 被切的蚯蚓的左右长度,我们能发现,先切蚯蚓的左部分 > 后切蚯蚓的左部分,右部分同理;
证明:
假设
先切蚯蚓总长为
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=a0−a1,
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
a0≥b0⇒a0×q≥b0×q⇒a1≥b1⇒a2≥b2
这时我们发现,除了第一次将全体元素放入队列时可能无序外,之后切出来的两段一定是有序的。
因此,我们可以使用三个普通队列而不使用优先队列,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");
}