NOIP2016 提高组 蚯蚓
算法一
容易想到用优先队列维护最大值,但是有 “其余蚯蚓长度增加 \(q\)” 这个条件,考虑怎么快速地处理。我们把增加的总长度记为偏移量 \(delta\)。每个数在加入前,把不产生贡献的时间的偏移量减去,再存进去就可以了。时间复杂度 \(O(mlogn)\),用 priority_queue 会被卡常,换成 heap 能卡过去。
#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
using namespace std;
using ll = long long;
const int LEN = 5e7;
char buf[100], *p1 = buf, *p2 = buf, obuf[LEN], *o = obuf;
inline int gc(){
return (p1 == p2) && (p2 = (p1 = buf) + fread(buf, 1, 100, stdin), p1 == p2) ? EOF : *p1++;
}
inline int rd(){
int x = 0; char ch; bool f = 1;
while(!isdigit(ch = gc())) f ^= (ch == '-');
do x = (x << 3) + (x << 1) + (ch ^ 48); while(isdigit(ch = gc()));
return f ? x : -x;
}
inline void output(int x){
if(x < 0){
x = ~x + 1;
*o++='-';
}
if(x > 9) output(x / 10);
*o++=(x % 10 + 48);
}
inline void write(int x, char c){
output(x);
*o++=c;
}
const int N = 1e5 + 5, M = 7e6 + 5;
int n, m, k, opt, cnt = 0; ll u, v;
int q[N + M];
signed main(){
// freopen("earthworm.in","r",stdin);
// freopen("earthworm.out","w",stdout);
n = rd(), m = rd(), k = rd(), u = rd(), v = rd(), opt = rd();
F(i, 1, n) q[++ cnt] = rd() - k;
make_heap(q + 1, q + cnt + 1);
F(t, 1, m){
pop_heap(q + 1, q + cnt + 1);
int ret = q[cnt] + t * k, x = ret * u / v, y = ret - x;
if(t % opt == 0) write(ret, ' ');
q[cnt] = x - (t + 1) * k;
push_heap(q + 1, q + cnt + 1);
q[++ cnt] = y - (t + 1) * k;
push_heap(q + 1, q + cnt + 1);
}
*o++='\n';
sort(q + 1, q + cnt + 1, [&](const int &i, const int &j){return i > j;});
F(i, 1, cnt) if(!(i % opt)) write(q[i] + (m + 1) * k, ' ');
fwrite(obuf, o - obuf, 1, stdout);
return fflush(0), 0;
}
算法二
对于每次的最大值 \(x\),容易发现 \(x\) 和 \(\lfloor px \rfloor\) 都具有单调性。想一下 \(x - \lfloor px \rfloor\) 有没有单调性。
先不考虑取整符号,可以化简为 \((1 - p)x\),考虑小数部分的话,这是具有单调性的。
加上取整符号呢?发现 $ x - \lfloor px \rfloor = \lceil (1 - p)x \rceil$,相当于会把一些数向右移动一点点变成最近的整数,容易理解这仍然具有不严格单调性。
所以三种值都具有不严格单减性。
所以我们直接开三个队列分别维护 \(x, \lfloor px \rfloor, x - \lfloor px \rfloor\),每次取出的最大值就是三个队首中的最大值。模拟找数删数的过程就做完了。
时间复杂度 \(O(nlogn + m)\)。
#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
#define mk make_pair
#define pii pair<int, int>
using namespace std;
using ll = long long;
char buf[100], *p1 = buf, *p2 = buf;
inline int gc(){
return (p1 == p2) && (p2 = (p1 = buf) + fread(buf, 1, 100, stdin), p1 == p2) ? EOF : *p1++;
}
inline int rd(){
int x = 0; char ch; bool f = 1;
while(!isdigit(ch = gc())) f ^= (ch == '-');
do x = (x << 3) + (x << 1) + (ch ^ 48); while(isdigit(ch = gc()));
return f ? x : -x;
}
const int N = 1e5 + 5;
const int inf = 2e9;
int n, m, k, t, delta = 0;
ll u, v;
int a[N];
queue<int> q[3];
signed main(){
n = rd(), m = rd(), k = rd(), u = rd(), v = rd(), t = rd();
F(i, 1, n) a[i] = rd();
sort(a + 1, a + n + 1, [&](const int &i, const int &j){return i > j;});
F(i, 1, n) q[0].push(a[i]);
F(i, 1, m){
pii mx = max({ mk(q[0].empty() ? -inf : q[0].front(), 0), mk(q[1].empty() ? -inf : q[1].front(), 1), mk(q[2].empty() ? -inf : q[2].front(), 2)});
q[mx.second].pop(), mx.first += delta, delta += k;
q[1].push(mx.first * u / v - delta), q[2].push(mx.first - mx.first * u / v - delta);
if(!(i % t)) printf("%d ", mx.first);
} puts("");
int cnt = 0;
while(q[0].size() || q[1].size() || q[2].size()){
pii mx = max({ mk(q[0].empty() ? -inf : q[0].front(), 0), mk(q[1].empty() ? -inf : q[1].front(), 1), mk(q[2].empty() ? -inf : q[2].front(), 2)});
q[mx.second].pop();
if(!((++ cnt) % t)) printf("%d ", mx.first + delta);
} return fflush(0), 0;
}
总结
本题中 \(m\) 过大的范围提示我们需要寻求一种更快速的线性维护方法,显然这个方法一定没有普适性,所以我们应该大胆猜想题目的情景有一些好用的性质,比如单调性、二分性等等。