51nod 1421 最大MOD值(高妙的调和级数复杂度)

有一个a数组,里面有n个整数。现在要从中找到两个数字(可以是同一个) ai,aj ,使得 ai mod aj 最大并且 ai  aj

Input
单组测试数据。
第一行包含一个整数n,表示数组a的大小。(1 ≤ n ≤ 2*10^5)
第二行有n个用空格分开的整数ai (1 ≤ ai ≤ 10^6)。
Output
输出一个整数代表最大的mod值。
Input示例
3
3 4 5
Output示例
2

题解:首先考虑mod的真正定义
a%b=a/b*b+c
思考一下其实是kb+c的形式,画在数轴上就是

很显然,如果有一个数x%a>b%a他肯定会在(k*a+b,(k+1)*a)的区间之中,到底有没有这些数存在可以用前缀和O(1)查询

所以因此我们就可以对c即余数进行二分了,不过二分的时候检验是O(nlogn)的,因为会枚举每一个数的倍数的区间,总复杂度大约是调和级数即logn 但其实仔细一想这是不满的

在套上二分,复杂度是O(nlognlogn) 

代码如下:

#pragma GCC optimize("inline",3)
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

int vis[4000010],sum[4000010],k,n,a[200010],cnt;

int check(int x)
{
    for(register int i=1;i<=cnt;++i)
    {
        for(register int j=a[i];j<=1e6;j+=a[i])
        {
            if(sum[j+a[i]-1]-sum[j+x-1]>0)
            {
                return 1;
            }
        }
    }
    return 0;
}

int main()
{
    scanf("%d",&n);
    int tmp;
    for(register int i=1;i<=n;++i)
    {
        scanf("%d",&tmp);
        if(!vis[tmp])
        {
            vis[tmp]=1;
        }
    }
    for(register int i=1;i<=2e6;++i)
    {
        sum[i]=sum[i-1]+vis[i];
    }
    for(int i=1;i<=1e6;i++)
    {
        if(vis[i])
        {
            a[++cnt]=i;
        }
    }
    register int l=0,r=2e6,mid;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(check(mid))
        {
            l=mid;
        }
        else
        {
            r=mid-1;
        }
        if(r-l<=1)
        {
            r=check(r)?r:l;
            break;
        }
    }
    printf("%d\n",r);
}

这个程序1e5跑跑是非常轻松的但是到了2e5就有点力不从心了

直接提交到51nod上就大概只能过18个点

于是考虑优化,其实只要知道了上面那个mod的定义,我们每次对于(k+1)*ai找到小于它的最大aj,那么这个aj%ai的值肯定是(k*ai,(k+1)*ai)区间内所有数%ai最大的

因此我们可以先预处理出f[i]为比i小的最大ai,然后在向上面一样枚举区间对于每个区间计算最大模数更新答案,因为省去了二分,复杂度就是O(nlogn)

代码如下:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

int b[2000010],a[200010],n,t,ans=-1;

int cmp(int a,int b)
{
    return a>b;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    sort(a+1,a+n+1,cmp);
    int t=unique(a+1,a+n+1)-a-1;
    n=t;
    int j=1;

    for(int i=2e6;i>=1;i--)
    {
        while(j<=n&&a[j]>=i) j++;
        b[i]=a[j];
    }    
    for(int i=1;i<=n;i++)
    {
        for(int j=2;j<=2e6/a[i];j++)
        {
            ans=max(ans,b[a[i]*j]%a[i]);
        }
    }
    printf("%d\n",ans);
}

 

 

yzy大佬用set随手A掉了此题
详见这里
https://blog.csdn.net/yzyyylx/article/details/81013038

posted @ 2018-07-12 13:43  Styx-ferryman  阅读(421)  评论(0编辑  收藏  举报