整除

定义

  a是b的因数,或b是a的倍数,记作a|b

  整除的性质:

    ①如果a|b,b|c,那么a|c;

    ②a|b且a|c等价于对于任意整数x、y满足a|(b*x+c*y);

    ③设m≠0,那么a|b等价于(m*a)|(m*b);

    ④设整数x、y满足a*x+b*y=1,且a|n,b|n,那么(a*b)|n;

      证明:∵a|n且b|n

         ∴(a*b)|(b*n)且(a*b)|(a*n)

         ∴(a*b)|(a*n*x+b*n*y)

         又∵a*n*x+b*n*y=(a*x+b*y)*n=n

         ∴(a*b)|n。

    ⑤若b=q*d+c,那么d|b的充要条件是d|c。

例1:church

  给出m*n个矩形,每个整数节点上有一个教堂,每个教堂与它八个方向的教堂有连边,求从某一点开始经过所有点的最短路径。

  范围:m、n≤10000

  首先我们没有特别好的思路,就考虑爆搜打表,打出表后比较和n、m的关系,我们发现对于一般的n、m,满足如果n*m为偶数,那么最少长度即为m*n,否则为m*n+0.414,由于m==1||n==1时不存在1.414的边,特判即可。

例2:strongbox

  一个密码满足都是[0,n-1]的整数,并且如果a是密码,b是密码,那么(a+b)%n都是密码,给出n、k和k个整数,其中1~k-1非密码,k为密码,求最多密码数。

  范围:1≤k≤250000,k≤n≤1014

  结论1:如果x是密码,那么x*k%n也是密码。

  证明:显然,它并没有要求a、b不相同,那么x的整数次倍一定是密码。

  结论2:如果x是密码,那么gcd(x,n)也是密码。

  证明:我们考虑x*k%n=gcd(x,n),我们可以转化为x*k-n*c=gcd(x,n)

       ∵对于二元一次方程ax+by=c,有整数解当且仅当c%gcd(a,b)==0

       ∴上述方程对于k一定有正整数解

       ∴gcd(x,n)也是密码。

  结论3:如果x、y是密码,那么gcd(x,y)也是密码

  证明:由结论1知(a*x+b*y)%n也是密码

       ∵a*x+b*y=gcd(x,y)一定有解

       ∴a*x+b*y≡gcd(x,y)(mod n)一定有解(相等即可)

       ∵a*x+b*y%n一定是密码

       ∴gcd(x,y)一定是密码

  

  接下来我们考虑如何利用这几个结论,考虑对于一个密码集合S,设A中所有数的gcd为x

  那么我们可以证明不存在比x小的数存在于S中,否则x将不是所有数的gcd

  所以S中的数即为x、2x、3x……

  因此我们要密码集合尽可能多,那么我们就是要x尽可能的小。

  所以对于一个密码,我们可以先用gcd(a[k],n)将a[k]缩小,可知x是a[k]的因数,但它不是其他任何非密码的因数,因此我们也可以gcd(a[i],a[k])把a[i]缩小,因为不是a[k]可以直接排除,这样之后的a[i]也一定是a[k]的因数。

代码

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

ll gcd(ll a,ll b){return !b?a:gcd(b,a%b);}
ll a[310000],fac[310000];
bool f[310000];
int main()
{
    ll n,k,cnt=0;
    scanf("%lld%lld",&n,&k);
    for(ll i=1;i<=k;i++)
        scanf("%lld",&a[i]);
    a[k]=gcd(a[k],n);
    for(ll i=1;i<k;i++)
        a[i]=gcd(a[i],a[k]);
    for(ll i=1;i*i<=a[k];i++)
        if(a[k]%i==0)
        {
            fac[++cnt]=i;
            if(i*i!=a[k])fac[++cnt]=a[k]/i;
        }
    sort(fac+1,fac+cnt+1);
    for(ll i=1;i<k;i++)
    {
        ll pos=lower_bound(fac+1,fac+cnt+1,a[i])-fac;
        if(pos<=cnt)f[pos]=1;
    }
    for(ll i=1;i<=cnt;i++)
        if(f[i])
        for(ll j=1;j<i;j++)
            if(fac[i]%fac[j]==0)f[j]=1;
    for(ll i=1;;i++)
        if(!f[i]){printf("%lld",n/fac[i]);break ;}
}

 

 

 

  

posted @ 2019-10-14 21:03  fbz  阅读(341)  评论(0编辑  收藏  举报