一道通过预处理使得无需使用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 ", ans); 47 return 0; 48 }