整除
定义
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 ;} }