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

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 1 0 1 2 2 2 2 2 2 1 1
 

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





评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值