51nod 1421 最大取模值 奇怪的数学方法

这题确实经典.我旁边的一些神仙都用奇怪的方法A掉了,而我只能选择了贺题.

题目描述

给出一个数组a,求当a[i]>=a[j]时a[i]%a[j]最大的值.n<=10^5,a[i]<=10^6.

瞎搞过程

首先想到的当然是 n2 n 2 的暴力.把暴力打上去有 40 40 分.
然后我们考虑一下,对于 a[i] a [ i ] 最大的取模结果必然是小于 a[i] a [ i ] 的.
我们从大到小枚举被取模数,当 a[i]ans a [ i ] ≤ a n s 直接跳出循环.

#include<bits/stdc++.h>
/*省略快读快写*/
}using namespace chtholly;
using namespace std;
const int yuzu=2e5;
int a[yuzu|10];

int main(){
int i,j,n=read(),llx=0;
for (i=1;i<=n;++i) a[i]=read();
sort(a+1,a+n+1);
for (i=n;i&&a[i]>llx;--i){
  for (j=i+1;j<=n;++j){
    llx=max(llx,a[j]%a[i]);
    }
  }write(llx);
}

这样从40分变成60分.加了卡常头文件之后分数并没有变化.
那么只能考虑打复杂度正确的算法了.
一开始我想二分,但是挂了,后来我发现答案并没有可二分性.
好吧,我放弃了.
那么接下来我们看一个标算.

/*
我们来看,对于某个被取模数x来讲,能够得到的最大的模数是小于x的某一个倍数的最大的那个数取模x的值.
可以看到数组是可以排序,去重的.
那么我们可以处理出一个2*数据范围的数组d,d[i]表示a数组中小于i的最大数字.
那么对于每一个去重后的a[i],我们都可以枚举它的每一个倍数a[i]*j,用d[a[i]*j]去模a[i],取最大值即可.
*/
#include<bits/stdc++.h>
/*快读快写省略*/
}using namespace chtholly;
using namespace std;
const int yuzu=2e5,mulu=2e6;
int a[yuzu|10],d[mulu|10],n;
map<int,int> mp;

int main(){
int i,j,k=read(),llx=0;
for (int x;k--;) if (!d[x=read()]++) a[++n]=x;
memset(d,0,sizeof d);
sort(a+1,a+n+1,greater<int>());
for (j=1,i=mulu;i;--i){
  for (;j<=n&&a[j]>=i;) ++j;
  d[i]=a[j];
  }
for (i=1;i<=n;++i){
  for (j=2;j*a[i]<=mulu;++j){
    llx=max(llx,d[a[i]*j]%a[i]);
    } 
  }write(llx);
}

接下来是神犇styx的二分算法.我没看懂.
可惜这个代码无论如何总有这么一两个点 tle t l e ,我们都没救过来.

#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);
}

然后是神仙yzy的set瞎搞算法.反正我还是不懂.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<set>
#define P pair<int,int>
#define mp make_pair
#define fi first
#define se second
#define N 200100
using namespace std;

int n,num[N],t,ans;
P tmp;
set<P>se;

int main()
{
    int i,j;
    cin>>n;
    for(i=1; i<=n; i++)
    {
        scanf("%d",&num[i]);
    }
    sort(num+1,num+n+1);
    se.insert(mp(num[2]/num[1]*num[1],num[1]));
    for(i=2; i<=n; i++)
    {
        for(;;)
        {
            tmp=(*se.begin());
            if(num[i]/tmp.se != tmp.fi/tmp.se)
            {
                se.erase(tmp);
                se.insert(mp(num[i]/tmp.se*tmp.se,tmp.se));
            }
            else
            {
                ans=max(ans,num[i]%tmp.se);
                break;
            }
        }
        se.insert(mp(num[i+1]/num[i]*num[i],num[i]));
    }
    cout<<ans;
}

好,谢谢大家.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值