HDU 6040& 2017年多校训练第一场 1008题

来源:互联网 发布:缤特力升级软件 编辑:程序博客网 时间:2024/06/06 02:41

2017年多校训练第一场 1008题

Problem Description
sd0061, the legend of Beihang University ACM-ICPC Team, retired last year leaving a group of noobs. Noobs have no idea how to deal with  coming contests. sd0061 has left a set of hints for them.

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.
 

Input
There are multiple test cases (about ).

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;}
 

Output
For each test case, output "Case #   " in one line (without quotes), where  indicates the case number starting from  and   denotes the rating of noob for the -th contest of corresponding case.
 

Sample Input
3 3 1 1 10 1 22 2 2 2 21 1
 

Sample Output
Case #1: 1 1 202755Case #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;}





原创粉丝点击