CodeForces - 485D

 

一道通过预处理使得无需使用lower_bound的题,最终的复杂度是O(MlogN),也算是对调和复杂度分析的一个练习吧

 

题意:给定不超过n(1<=n<=200000)个数,每个数介于1~1e6之间,选定两个数使得ai%aj最大

 

思路:对于一个值aj,我们要找到ai使ans=ai%aj最大,ans显然小于aj。我们希望ans越大,就要求ai越靠近且略小于aj的倍数。由于给出的数列的值是有上界的,我们对于从[aj, aj + aj)开始的每段区间长度为aj的段,找出那个最大的数,然后更新ans的值即可。问题在于如何找到那个最大的数?第一反应可能会想到先对数组进行排序,然后再利用lower_bound进行二分搜索。这样以来复杂度是(NlogN + MlogMlogN),可不可以卡过没有尝试过。

  但是重点在与,我们对于每个不大于2*上界的值,我们可以预处理出给定的数列里不大于它的最大值,这个过程只需要O(M)就可以实现了,这样以来,查询过程的复杂度就是O(1),总的复杂度就是O(MlogN)了,强无敌啊。

 

  我开了两个数组,pre[i]用于记录题目给定数列中小于i的最大值,a[i]用于记录i是否在原数列中出现过。枚举所有值不同的aj,去更新ans。最多N个aj,每个在第二层循环运行(2 * 上界 / aj) - 1次,那就约为一个上界*调和级数的复杂度。最坏情况下,(1+1/2+1/3+...+1/N),调和级数的值为log(N) + C(欧拉常数),所以总的复杂度是O(MlogN)

 

 1 #include <queue>
 2 #include <vector>
 3 #include <cstdio>
 4 #include <cstring>
 5 #include <iostream>
 6 #include <algorithm>
 7 #define INF 0x3f3f3f3f
 8 #define MOD 1000000007
 9 using namespace std;
10 typedef long long LL;
11 
12 const int maxm = 1e6;
13 int pre[maxm * 2 + 10];
14 bool a[maxm + 10];
15 int N;
16 
17 
18 int main(int argc, const char * argv[]) {
19     scanf("%d", &N);
20     for (int i = 1; i <= N; i++) {
21         int val;
22         scanf("%d", &val);
23         pre[val] = -1;
24         a[val] = true;
25     }
26     //预处理
27     int small = 0;
28     int big = small;
29     while (big <= maxm * 2) {
30         while (big <= maxm * 2 && pre[big] != -1) {
31             big++;
32         }
33         for (int i = small + 1; i <= big; i++) {
34             pre[i] = small;
35         }
36         small = big;
37     }
38     
39     int ans = 0;
40     for (int i = 1; i <= maxm; i++) {
41         if (!a[i]) continue;
42         for (int j = i + i; j <= maxm * 2; j += i) {
43             ans = max(ans, pre[j] % i);
44         }
45     }
46     printf("%d\n", ans);
47     return 0;
48 }
View Code

 

posted @ 2017-08-09 12:52  xFANx  阅读(291)  评论(0编辑  收藏  举报