Sweety

Practice makes perfect

导航

求个最大值(技巧+二分转化)

Posted on 2017-05-03 17:25  蓝空  阅读(268)  评论(0编辑  收藏  举报

求个最大值

时间限制: 1 Sec  内存限制: 128 MB
提交: 267  解决: 52
[提交][状态][讨论版]

题目描述

给出 n(1 <= n <= 200000)个数字 ai(1 <= ai <= 1000000),i 为数字的下标,按输入顺序从 1 开始编号
一直到 n,求满足 ai >= aj 的最大的 ai % aj。 

输入

第一行一个数字 n,第二行 n 个整数。 

输出

题目要求的最大值。 

样例输入

2
2 3

样例输出

1

提示


题解:

排序去重之后,对1到 max{ai}中的每个数字x求出小于x的最大 ai,记为 b[x]。 对于每个 ai 枚举它的倍数即可,时间复杂度 O(max{ai} + nlogn) 。 

这题比赛里已经想到思路了,就是不知道怎么去表示。。 思路很简单,设a, b , a > b -> a/b = k....r(r是余数),这样我们肯定希望a离着k*b越元越好。也就是我们枚举倍数的时候(比赛中写过枚举倍数,但是n*n的。。)我们希望b*k离着a越远越好,但是不能远过b的大小,这样就难处理了,但是可以转化成 (k+x)*b离着a越近越好,那枚举到每个数的倍数时候,对他有用的就只有一个离着这个数最近的a【i】了、、前缀处理下就好了。。记录1-maxn*2每个数比他小但是最大的a[i]就行。。


这个问题中有几个点还是比较巧妙的!!!

(1)求的是a[j]>a[i] 且a[j]%a[i]的最大值,如果把研究a[j]的话问题是不好解决的,需要转化一下,也就是上面的这个转化过程,用倍数来想

(2)开始的时候我是感觉这样会超时的,因为开始的时候如果a[i]=2,这需要(200000*2)/2=200000的,然后每个数按照这种最坏的想,也就是200000*20000,这明显是会超时的,但是之后计算之后发现最大值是134565766,也就是说复杂度是1e7,不会超时的,其实这就是个调和级数计算方式(调和级数的值:n趋近与无穷后是:1/1+1/2+1/3...+1/n~ln(n+1) +r,r是欧拉常数,r=0.5772156649.),就是高数级数里面一个简单的概念好不好,都忘了。。还有这里其实就是放缩放错了,放缩方法分两种的好不好,张宇十八讲上好像看过敲打,趋近于无穷的是不能简单用最大的放缩的。。。

(3)想要找到最接近a[j]的某个值,第一想法肯定是二分思想比较快,但是这里又是做了一个巧妙的思想,就是用了这个辅助数组,所以查找就直接是O(1)了,相当巧妙,学习了!!!


#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e6 + 10;
int b[maxn], a[maxn];
int main()
{
    int n;
    while(~scanf("%d", &n))
    {
        memset(b, 0, sizeof(b));
        for(int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        sort(a+1, a+1+n);
        int index = a[1]+1;
        a[n+1] = maxn*2;
        for(int i = 2; i <= n+1; i++) ///记录比j小的最大的a[i]
            while(index <= a[i])
                b[index++] = a[i-1];
        int ans = 0;
        for(int i = 1; i <= n; i++)
            for(int j = a[i]*2; j <= maxn*2; j += a[i])
                ans = max(ans, b[j]%a[i]);
        printf("%d\n", ans) ;
    }
    return 0;
}


还有网上有的说二分可以,按照上面复杂度计算是不可以的,并且竞赛之后发现已经回TL了!!!