There are noobs in the team, the -th of which has a rating . sd0061 prepares one hint for each contest. The hint for the -th contest is a number , which means that the noob with the -th lowest rating is ordained by sd0061 for the -th contest.
The coach asks constroy to make a list of contestants. constroy looks into these hints and finds out: is satisfied if and .
Now, you are in charge of making the list for constroy.
For each test case:
The first line contains five integers .
The second line contains integers, the -th of which is the number of the -th hint.
The noobs' ratings are obtained by calling following function times, the -th result of which is .
unsigned x = A, y = B, z = C; unsigned rng61() { unsigned t; x ^= x << 16; x ^= x >> 5; x ^= x << 1; t = x; x = y; y = z; z = t ^ x ^ y; return z; }
3 3 1 1 1 0 1 2 2 2 2 2 2 1 1
Case #1: 1 1 202755 Case #2: 405510 405510
题目大意:隔壁大佬一句话总结就是,O(N)时间内求第K大。
他还加了一句,我找到网上的板了,然后我们就没一个人管他了,让他自生自灭了。结果是,GG。
首先题目会给你一串乱序数字,数据规模达到了 1e7 ,然后最多有100个询问。100 * 1e7 = 1e9 大概是 10s, 所以简单的O(N)查找肯定GG了。
注意到题目里面有个条件:constroy looks into these hints and finds out: is satisfied if and .
对于询问有个快速上升的趋势,后面的询问 K 如果大于前面的询问,则K要必前面询问中任意两个数值的和大。
经过一个简单的推理,就差不多是: 0,1,2,3,5,8,......在1e7范围内,这个上升速度也是极快的,反正不用到第一百个数就比1e7大了。
所以,由此 可见,我们需要的询问数量比log2(1e7)多那么一点点,然后我们从大范围一个一个找到小范围,最后就能近似于O(N)的时间内完成了。
正确的解法:(perfect)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e7 + 9;
unsigned a[maxn], ans[105];
int b[105], id[105];
int n, m;
unsigned x, y, z;
inline unsigned rng61() {
unsigned t;
x ^= x << 16;
x ^= x >> 5;
x ^= x << 1;
t = x;
x = y;
y = z;
z = t ^ x ^ y;
return z;
}
void change(unsigned & a, unsigned & b) {
unsigned t;
t = a; a = b; b = t;
}
int Rand(int l, int r) {
int tmp = rand() % (r - l + 1) + l;
change(a[l], a[tmp]);
unsigned key = a[l];
int i = l + 1, j = r;
while(i <= j) {
while((i <= r) && a[i] <= key) i++;
while((j >= l) && a[j] > key) j--;
if(i < j) change(a[i], a[j]);
}
/*
int cnt = l;
for(int i = l + 1; i <= r; i++) {
if(a[i] < a[l]) change(a[++cnt], a[i]);
}
change(a[l], a[cnt]);
return cnt;
*/
change(a[l], a[j]); return j;
}
unsigned check(int l, int r, int k) {
int pos;
while(k != (pos = Rand(l, r))) {
if(k < pos) r = pos - 1;
else l = pos + 1;
}
return a[k];
}
int cmp(int x, int y) {
return b[x] < b[y];
}
int main() {
//freopen("1008.in", "r", stdin);
//freopen("out.out", "w", stdout);
srand(time(NULL));
for(int kase = 1; ~scanf("%d%d%u%u%u", &n, &m, &x, &y, &z); kase++) {
for(int i = 1; i <= n; i++) {
a[i] = rng61();
}
for(int i = 1; i <= m; i++) {
scanf("%d", &b[i]); b[i]++; id[i] = i;
}
sort(id + 1, id + 1 + m, cmp);
ans[id[m]] = check(1, n, b[id[m]]);
for(int i = m - 1; i > 0; i--) {
if(b[id[i]] == b[id[i + 1]]) {
ans[id[i]] = ans[id[i + 1]];
continue;
}
ans[id[i]] = check(1, b[id[i + 1]], b[id[i]]);
}
printf("Case #%d: ", kase);
for(int i = 1; i <= m; i++) printf("%u%c", ans[i], i == m ? '\n' : ' ');
}
return 0;
}
然后我再介绍一个被随机数看命运的一个写法:
如果按照官方思路,先找BK,然后缩区间找B(K - 1) 然后继续缩,一直到结束。但是对于第B(K - 1)大的数,我们在找第BK大的数的时候,可能一句找到了一个第M大的数,这个数比第B(K - 1)小。如果这样的话,我们直接在M和BK之间搜第B(K - 1)就好了。理论上来讲,应该是比每次都是缩区间然后整个区间查询的速度要快一点的。
想法说完了,说方案:
我用了一个数组来映射需要查询的第K大的数,也就是sum[question[i] + 1] = 1,然后处理sum数组,求前缀和。区间[ i, j ]是否需要去查询的判断条件就是sum[j] - sum[i - 1] 是不是大于0,大于0代表这个区间内有需要查询的,否则,这块乱序的数字就不用管了。
对于我自己的思路,我直接改了快排的实现,快排是需要对当前分出来的左右区间都要去查询一遍,这里只要在查询之前加个判断,就可以避开不必要的查询。理想状态下应该是比标程更优的。
然而,可能是自己手写的快排真的太靠天意了,在随机数的影响下,按这个思路写出来的程序运行时间在 2.10 - 2.80之间徘徊。(哇,天知道为什么会这样。)补题的时候恶意提交了100发,大约过了50发,另外50发T了,可能这就是看脸的游戏了吧。/(ㄒoㄒ)/
代码如下:
#include <bits/stdc++.h>
using namespace std;
inline void change(unsigned & a, unsigned & b) {
unsigned c;
c = a; a = b; b = c;
}
unsigned x, y, z;
unsigned rng61() {
unsigned t;
x ^= x << 16;
x ^= x >> 5;
x ^= x << 1;
t = x;
x = y;
y = z;
z = t ^ x ^ y;
return z;
}
int n, m;
const int maxn = 1e7 + 50;
unsigned A, B, C;
unsigned num[maxn];
int sum[maxn], question[105];
int cnt;
void solve(int l, int r) {
if(l < r) {
//int k = (l + r) >> 1;
int k = rand() % (r - l + 1) + l;
change(num[l], num[k]);
//int k = l;
unsigned key = num[l];
int i = l + 1, j = r;
while(i <= j) {
while((i <= r) && num[i] <= key) i++;
while((j >= l) && num[j] > key) j--;
if(i < j) change(num[i], num[j]);
}
change(num[l], num[j]);
if(sum[j - 1] - sum[l - 1]) solve(l, j - 1);
if(sum[r] - sum[j]) solve(j + 1, r);
}
}
int main() {
//freopen("1008.in", "r", stdin);
//freopen("out.out", "w", stdout);
int kase = 0;
srand(time(NULL));
while(~scanf("%d%d%u%u%u", &n, &m, &A, &B, &C)) {
cnt = 0;
x = A, y = B, z = C;
memset(sum, 0, sizeof(sum));
for(int i = 1; i <= n; i++) num[i] = rng61();
for(int i = 1; i <= m; i++) {
scanf("%d", &question[i]);
sum[question[i] + 1] = 1;
}
for(int i = 1; i <= n; i++) sum[i] += sum[i - 1];
solve(1, n);
printf("Case #%d:", ++kase);
for(int i = 1; i <= m; i++) {
printf(" %u", num[question[i] + 1]);
}
printf("\n");
}
return 0;
}