最近一周都在刷扩展欧几里得、逆元、整除之类的数论专题,颇有心得,决定写一发。虽然说是扩展欧几里得专题,但是其实里面加了几道普通数论题,不想做的可以跳过。因为我是随便找的题目,所以题目的难度不是按照我的排版来的(前面的题目可能很难,后面的题目可能很简单),请读者见谅。
对于数论的基础知识和扩展欧几里得的算法,我这里不再赘述,不会的请参考这位大大的博客,我觉得写得非常不错。
http://www.cnblogs.com/frog112111/archive/2012/08/19/2646012.html
这里我再附赠一个扩展欧几里得的模版
long long exgcd(long long a,long long b,long long& x,long long& y) { if(!b) { x=1; y=0; return a; } long long r=exgcd(b,a%b,y,x); y-=a/b*x; return r; }
第一题 poj-1061 青蛙的约会
分析:最经典的一道题。这是一个模线性方程的题,设需要跳k次才可以相遇,从题意可以得到等式x+m*k=y+n*k,因为纬线是L循环的,转化一下可以得到(m-n)*k=y-x(mod L)。然后就是套模版,没什么坑点,记得用long long。
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 30+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define push_back PB typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; ll exgcd(ll a,ll b,ll& x,ll& y) { if(!b) { x=1; y=0; return a; } ll t=exgcd(b,a%b,y,x); y-=a/b*x; return t; } bool lq(ll a,ll b,ll n,ll& ans) { ll x,y; ll d=exgcd(a,n,x,y); if(b%d)return false; ll s=n/d; ans=(x*b/d%s+s)%s; return true; } int main() { //freopen("d:\\acm\\in.in","r",stdin); ll x,y,n,m,l; while(~scanf("%lld %lld %lld %lld %lld",&x,&y,&n,&m,&l)) { ll ans; m-=n; x-=y; if(m<0)m=-m,x=-x; if(lq(m,x,l,ans))printf("%lld\n",ans); else puts("Impossible"); } return 0; }
分析:读懂题意,也是一道模线性方程的题。设需要x步,则A+C*x=B(mod 2^k),整理一下C*x=B-A(mod 2^k)。也没有坑点(=。=,这题解写的)。
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 100000+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define PB push_back typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; ll exgcd(ll a,ll b,ll& x,ll &y) { if(!b) { x=1; y=0; return a; } ll t=exgcd(b,a%b,y,x); y-=a/b*x; return t; } bool ql(ll a,ll b,ll n,ll& ans) { ll x,y; ll d=exgcd(a,n,x,y); if(b%d)return false; ll s=n/d; ans=(b/d*x%s+s)%s; return true; } int main() { //freopen("d:\\acm\\in.in","r",stdin); ll a,b,c,k; while(scanf("%lld %lld %lld %lld",&a,&b,&c,&k),a||b||c||k) { ll M=(ll)1<<k; ll ans; if(ql(c,b-a,M,ans))printf("%lld\n",ans); else puts("FOREVER"); } return 0; }
分析:这是一道整数快速幂的题。看懂题意,就是把Ai^Bi%M(1<=i<=H)都加起来,取模。
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 100000+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define PB push_back typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; ll m; ll qlow(ll a,ll n) { ll ans=1; while(n) { if(n&1)ans=ans*a%m; a=a*a%m; n>>=1; } return ans; } int main() { //freopen("d:\\acm\\in.in","r",stdin); int t; scanf("%d",&t); while(t--) { int n; scanf("%lld",&m); scanf("%d",&n); ll ans=0; while(n--) { ll a,b; scanf("%lld %lld",&a,&b); ans=(ans+qlow(a,b))%m; } printf("%lld\n",ans); } return 0; }
分析:更简单的一道题,就是一个验证。直接快速幂验证一下,还要判断一下是不是素数。你要是牛逼一点的话可以用米勒拉宾素数测试,但是其实并不需要,直接最简单的判断一下就行了。
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 100000+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define PB push_back typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; ll m; ll qlow(ll a,ll n) { ll ans=1; while(n) { if(n&1)ans=ans*a%m; a=a*a%m; n>>=1; } return ans; } bool ju(ll a) { for(int i=2;i<=sqrt(a);i++) { if(a%i==0)return false; } return true; } int main() { //freopen("d:\\acm\\in.in","r",stdin); ll p,a; while(scanf("%lld %lld",&p,&a),p||a) { m=p; ll ans=qlow(a,p); if(ans==(a%p)) { if(ju(p))puts("no"); else puts("yes"); } else puts("no"); } return 0; }
题目大意是给你无限个a(mg)和b(mg)的砝码,问至少需要多少个砝码才能称出c(mg)重的物品。
分析:这是一道不定方程题,但是最后得到通解的时候需要进行取舍(问题问的是最少的砝码)。不妨设需要x个a(mg)的砝码和y个b(mg)的砝码,(x,y如果是负数,那么代表这些砝码要放到物品的一边),方程显然是a*x+b*y=c。设d是gcd(a,b),那么我们先来看不定方程的通解是x=x0+b/d*t,y=y0-a/d*t(t是系数),这是直线的参数方程。我们发现这条直线的斜率是负数,那么|x|+|y|的最小值一定在这条直线与x轴、y轴的交点附近取到(因为一定要取整数,不明白的情去画图)。那么令x=0,y=0,解得 t1=-x0/(b/d),t2=y0/(a/d),那么使得|x|+|y|的t一定在t1或者t2附近。不过这道题目数据比较小,所以我直接全都扫了一下。=,=
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 100000+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define PB push_back typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; ll abs(ll x) { if(x>=0)return x; return -x; } ll exgcd(ll a,ll b,ll& x,ll& y) { if(!b) { x=1; y=0; return a; } ll t=exgcd(b,a%b,y,x); y-=a/b*x; return t; } void ql(ll a,ll b,ll c,ll& x,ll& y) { ll x0,y0; ll d=exgcd(a,b,x,y); ll h=c/d; x*=h; y*=h; ll k1=b/d; ll k2=a/d; ll s=-x/k1; ll e=y/k2; if(s>e)swap(s,e); ll minn=(ll)1*inf*inf; for(ll i=s-1;i<=e+1;i++) { ll tx=x+k1*i; ll ty=y-k2*i; if(abs(tx)+abs(ty)<minn||abs(tx)+abs(ty)==minn&&(a*abs(tx)+b*abs(ty))<(a*abs(x0)+b*abs(y0))) { x0=tx; y0=ty; minn=abs(x0)+abs(y0); } } x=abs(x0); y=abs(y0); } int main() { //freopen("d:\\acm\\in.in","r",stdin); ll a,b,c; while(scanf("%lld %lld %lld",&a,&b,&c),a||b||c) { ll x,y; ql(a,b,c,x,y); printf("%lld %lld\n",x,y); } return 0; }
分析:这是一道同余方程组,而且ai之间不是两两互质的,所以不可以用中国剩余定理。
当k==1时,显然给出了r0是最小的满足第一个同余方程的数(当然也只有一个同余方程);
当k>1时,考虑前两个同余方程,并且设满足前两个方程的最小整数是C
则有这两个同于方程是
C=r0(mod a0) ...........................(1)
C=r1(mod a1) ...........................(2)
那么根据(1)可以知道C=r0+k0*a0...........................(*)
将C代入(2)得到r0+k0*a0=r1(mod a1)
整理得a0*k0=r1-r0(mod a1)...........................(&)
这是一个模线性方程,设d=gcd(a0,a1)
使用 扩展欧几里得得到通解为
k0=x0+(a1/d)*t(t是系数)
将解得的k0代入(*)得到
C=r0+[x0+(a1/d)*t]*a0=r0+a0*x0+(a0*a1/d)*t
可以看见(a0*a1/d)=a0*a1/gcd(a0,a1)=lcm(a0,a1)
那么我们得到了一个新的同余方程C=r0+a0*x0(mod lcm(a0,a1))
显然我们可以看出这个C是满足(1)式的,接下来要证明下C也是满足(2)式的
显然lcm(a0,a1)=0(mod a1),所以C=r0+a0*x0(mod a1)
然后根据前面(&)式,可以知道r0+a0*x0=r1(mod a1)
所以C满足(2)式
事实上,我们将两个同余方程合并为了一个同余方程。
那么我们按照这种思路可以将所有的同余方程组合并为一个同余方程,
那么一个同余方程怎么解呢?请参照k==1的情况!
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 1000+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define PB push_back typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; ll aa[maxn]; ll r[maxn]; ll exgcd(ll a,ll b,ll& x,ll& y) { if(!b) { x=1; y=0; return a; } ll t=exgcd(b,a%b,y,x); y-=a/b*x; return t; } bool ql(ll a,ll b,ll n,ll& ans,ll& d) { ll x,y; d=exgcd(a,n,x,y); if(b%d)return false; ll s=n/d; ans=(b/d*x%s+s)%s; return true; } int main() { //freopen("d:\\acm\\in.in","r",stdin); int k; while(~scanf("%d",&k)) { for(int i=0;i<k;i++) scanf("%lld %lld",&aa[i],&r[i]); if(k==1) { printf("%lld\n",r[0]); continue; } ll a0=aa[0],a1,r0=r[0],r1,ans,d; int num; for(num=1;num<k;num++) { a1=aa[num]; r1=r[num]; if(!ql(a0,r1-r0,a1,ans,d))break; ll t=a0; a0=a0*a1/d; r0=(t*ans+r0)%a0; } if(num==k)printf("%lld\n",r0); else puts("-1"); } return 0; }
分析:经典的数论题。题目问的是n!能被多少个k整除。
我们先看当k为质数的时候,n!能被多少个质数k整除,
我们可以发现一个结论n!能被[n/k]+[n/(k^2)]+[n/(k^3)]+[n/(k^4)]+......个k整除(一直加到n/(k^N)=0的时候,后面的都是0了)
那么为什么是质数呢,可以知道如果是合数,上面的结论不可以用,因为可能是n!中的某个i和j各自给出一个质因数正好凑到了k。=,=
所以我们只能将k分解质因数,用k的质因数去求n!中有多少个此类质因数。
还要注意一点,k的质因数不一定只有1次方,万一有多次方,那么需要将总的可以整除的个数除以这个质因数的次方数。
取所有质因数取到个数的最小值(木桶理论),就是n!最多能被多少个k整除的个数。
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 10000000+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define push_back PB typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; int tot; bool not_prime[maxn]; ll prime[2000000]; void oula(int n) { tot=0; for(int i=2;i<=n;i++) { if(!not_prime[i]) prime[tot++]=i; for(int j=1;prime[j]*i<=n&&prime[j]*i>0;j++) { not_prime[prime[j]*i]=true; if(i%prime[j]==0)break; } } } ll cal(ll n,ll p) { ll ans=0; while(n) { n/=p; ans+=n; } return ans; } int main() { //freopen("d:\\acm\\in.in","r",stdin); oula(10000000); int t; scanf("%d",&t); for(int cas=1;cas<=t;cas++) { ll n,k; scanf("%lld %lld",&n,&k); if(k==1) { printf("Case %d: inf\n",cas); continue; } ll minn=(ll)1*inf*inf; for(int i=0;i<tot&&prime[i]<=k;i++) { if(k%prime[i]==0) { int c=0; while(k%prime[i]==0) c++,k/=prime[i]; ll t=cal(n,prime[i])/c; minn=min(minn,t); } } if(k>1) { ll t=cal(n,k); minn=min(minn,t); } printf("Case %d: %lld\n",cas,minn); } return 0; }
分析:简单的整除问题,因为数据很小,直接暴力把。枚举所有最后两位,看看能不能整除即可。
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 0+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define push_back PB typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; int main() { //freopen("d:\\acm\\in.in","r",stdin); int a,b; while(scanf("%d %d",&a,&b),a||b) { int flag=0; for(int i=0;i<100;i++) if((a*100+i)%b==0) { if(!flag)printf("%02d",i); else printf(" %02d",i); flag=1; } puts(""); } return 0; }
分析:已知gcd(a,b)和一个数a,求另一个数b的最小值。简单数论。
那么b显然要是gcd(a,b)的倍数,但是题目中说b!=gcd(a,b),那么我们就是求比gcd(a,b)稍微大一点的b。
只要找到一个最小的质因数k,满足gcd(a,b)里面k的次方数等于a里面k的次方数(两个都是k的0次方也可以),
那么直接将k*gcd(a,b)就是要求的答案。
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 1000+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define push_back PB typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; bool not_prime[maxn]; int tot; int prime[maxn]; void init(int n) { tot=0; for(int i=2;i<=n;i++) { if(!not_prime[i])prime[tot++]=i; for(int j=0;prime[j]*i<=n;j++) { not_prime[prime[j]*i]=true; if(i%prime[j]==0)break; } } } int main() { //freopen("d:\\acm\\in.in","r",stdin); init(1000); int t; scanf("%d",&t); while(t--) { int a,b; scanf("%d %d",&a,&b); for(int i=0;i<tot;i++) { int c=1; while(a%prime[i]==0) { c*=prime[i]; a/=prime[i]; } if(b%c==0) { b*=prime[i]; break; } } printf("%d\n",b); } return 0; }
分析:求逆元的裸题,求出B对于9973的逆元,与A相乘再取下模。
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 100000+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define PB push_back typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; ll exgcd(ll a,ll b,ll& x,ll& y) { if(!b) { x=1; y=0; return a; } ll t=exgcd(b,a%b,y,x); y-=a/b*x; return t; } ll niyuan(ll a,ll n) { ll x,y; exgcd(a,n,x,y); return (x%n+n)%n; } int main() { //freopen("d:\\acm\\in.in","r",stdin); int t; scanf("%d",&t); while(t--) { ll n,b; scanf("%lld %lld",&n,&b); ll ans=niyuan(b,9973); printf("%lld\n",n*ans%9973); } return 0; }
分析:一道裸的不定方程题。直接套模版,题目要求非负最小的x,稍微处理下。
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 100000+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define PB push_back typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; ll exgcd(ll a,ll b,ll& x,ll& y) { if(!b) { x=1; y=0; return a; } ll t=exgcd(b,a%b,y,x); y-=a/b*x; return t; } bool ql(ll a,ll b,ll c,ll& x,ll& y) { ll d=exgcd(a,b,x,y); if(c%d)return false; ll h=c/d; x*=h; y*=h; ll k1=b/d; ll k2=a/d; if(x>=0) { y-=x/k1*k2; x%=k1; } else { while(x<0) { x+=k1; y-=k2; } } return true; } int main() { //freopen("d:\\acm\\in.in","r",stdin); ll a,b; while(~scanf("%lld %lld",&a,&b)) { ll x,y,c=1; if(ql(a,b,c,x,y))printf("%lld %lld\n",x,y); else puts("sorry"); } return 0; }
分析:煞笔题,告诉你gcd和lcm求一组a和b。那么简单点就是gcd=a,lcm=b。
当然需要判断下gcd是否能够整除lcm,如果可以的话,直接把输入输出就可以了,=。=
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 30+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define push_back PB typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; int main() { //freopen("d:\\acm\\in.in","r",stdin); int t; scanf("%d",&t); while(t--) { int a,b; scanf("%d %d",&a,&b); if(b%a!=0)puts("-1"); else printf("%d %d\n",a,b); } return 0; }
分析:和前面一道题有些相似,这次是告诉你lcm(a,b)和一个数a,让你求最小的b。
那么这里有两种方法,第一种:
令 伪b=lcm(a,b)/a 为什么叫伪b呢? 因为如果gcd(伪b,a)=1的话,伪b才等于b,
否则因为伪b与a之间有重复的质因数导致了最后的lcm小于要求的。那么怎么办?
将重复的部分从a和lcm(a,b)中去掉(或者将这重复的部分补上给伪b,但是这样代码比较难写),但是真正的b是需要这一部分的,要给b累乘上,这样就对了吗?
不对!还要看看gcd(a,伪b)是否等于1,如果不等,那么说明你去掉重复部分后还有重复部分,还得去。
一直去直到gcd(a,伪b)==1,而且这些重复的部分需要给b累乘上,最后再乘个伪b。才是正确结果
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 100+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define PB push_back typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; int main() { //freopen("d:\\acm\\in.in","r",stdin); int t; scanf("%d",&t); while(t--) { int a,c; scanf("%d %d",&a,&c); if(c%a) { puts("NO SOLUTION"); continue; } int b=1; int tb=c/a; int h=__gcd(a,tb); while(h!=1) { b*=h; a/=h; c/=h; tb=c/a; h=__gcd(a,tb); } b*=c/a; printf("%d\n",b); } return 0; }上面那种方法是不是麻烦死了,那么第二种方法就非常简单。
直接分解lcm的质因数,看看这种质因数在a里面的次方数是不是与lcm里面的一样
如果一样,那么b里面就不需要了,只需要去质因数的最大次方,为了使b最小所以直接乘1
如果不一样,那么这个质因数的最大次方b就应该包含,否则得不到这个lcm
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 10000+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define PB push_back typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; bool not_prime[maxn]; int prime[maxn]; int tot=0; void init(int n) { for(int i=2;i<=n;i++) { if(!not_prime[i]) prime[tot++]=i; for(int j=0;prime[j]*i<=n&&prime[j]*i>0;j++) { not_prime[prime[j]*i]=true; if(i%prime[j]==0)break; } } } int main() { //freopen("d:\\acm\\in.in","r",stdin); int t; init(10000); scanf("%d",&t); while(t--) { int a,c; scanf("%d %d",&a,&c); if(c%a) { puts("NO SOLUTION"); continue; } int b=1; for(int i=0;i<tot&&prime[i]<=c;i++) { if(c%prime[i]==0) { int k=1; while(c%prime[i]==0) { c/=prime[i]; k*=prime[i]; } if(a%k)b*=k; } } if(c>1) { if(a%c)b*=c; } printf("%d\n",b); } return 0; }
分析:一道简单的组合数学题,一眼就可以看出答案是C(n+k-1,k-1),不会的去看清华慕课=。=
但是求解的时候麻烦了,本来想用lucas定理的,但是找不到模版,自己写还写崩了。=。=
最后只能用C(n,m)=(n-1,m)+C(n-1,m-1),利用dp水过,真是惨!
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 10000+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define PB push_back typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; ll dp[205][205]; ll cw(ll n,ll m,ll p) { if(dp[n][m]>0)return dp[n][m]; if(n==m||m==0)return dp[n][m]=1; return dp[n][m]=(cw(n-1,m-1,p)+cw(n-1,m,p))%p; } int main() { //freopen("d:\\acm\\in.in","r",stdin); ll n,k; clr(dp,-1); while(scanf("%lld %lld",&n,&k),n||k) { printf("%lld\n",cw(n+k-1,k-1,1000000)); } return 0; }
分析:和第七题一样,代码都不想贴了。
第十六题 zoj-3609
分析:好坑啊,这是我做过的最坑的一道数论题。题目很简单,就是求A对于M的逆元,但是就是WA。
一开始我以为是我小数据没考虑到,特地把m==1的时候全都修改成Not Exist,但是还是WA。
最后去看别人的题解的时候醉了,妈蛋,要特判m==1的时候为1,我也是无语了。
题目一点没有给出相关信息,谁能做的出来,真的坑啊。
如果比赛的时候来一道这种题目,大家都得完蛋!
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 1000+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define PB push_back typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; ll exgcd(ll a,ll b,ll& x,ll& y) { if(!b) { x=1; y=0; return a; } ll t=exgcd(b,a%b,y,x); y-=a/b*x; return t; } void niyuan(ll a,ll n) { if(n==1) { puts("1"); return ; } ll x,y; ll d=exgcd(a,n,x,y); if(d!=1) { puts("Not Exist"); return; } printf("%lld\n",(x%n+n)%n); } int main() { //freopen("d:\\acm\\in.in","r",stdin); int t; scanf("%d",&t); while(t--) { ll a,m; scanf("%lld %lld",&a,&m); niyuan(a,m); } return 0; }
第十七题 zoj-3593
分析:和第五题有点相似,但是有点不一样
问题大意是从A到B,每次可以走a,b,c=a+b个单位,问最少需要走多少步?
一开始以为只要a*x+b*y=B-A,然后在两个交点处特判一下就好了(不明白交点什么意思的滚去烂第五题)。
如果x与y同符号,那么x与y取绝对值,然后输出两者最大值;如果是相反符号,那么取绝对值相加,在两个交点处搜索一下。但是不知道为什么就是WA,写了好几遍一点没用。
后来实在是崩溃了,我只能套用第五题的模版,多次判断。
分别判断a*x+b*y=B-A、a*x+c*y=B-A和b*x+c*y=B-A的最小的|x|+|y|,输出,这才过了!
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 1000+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define PB push_back typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; ll exgcd(ll a,ll b,ll& x,ll& y) { if(!b) { x=1; y=0; return a; } ll t=exgcd(b,a%b,y,x); y-=a/b*x; return t; } ll cal(ll a,ll b) { return abs(a)+abs(b); } bool ql(ll a,ll b,ll c,ll& ans) { ll x0,y0; ll d=exgcd(a,b,x0,y0); if(c%d)return false; ll h=c/d; x0*=h; y0*=h; ll k1=b/d; ll k2=a/d; ll s=-x0/k1; ll e=y0/k2; ans=cal(x0,y0); for(ll i=s-3;i<=s+3;i++) { ll tx=x0+k1*i; ll ty=y0-k2*i; ll tt=cal(tx,ty); ans=min(ans,tt); } for(ll i=e-3;i<=e+3;i++) { ll tx=x0+k1*i; ll ty=y0-k2*i; ll tt=cal(tx,ty); ans=min(ans,tt); } return true; } int main() { //freopen("d:\\acm\\in.in","r",stdin); int t; scanf("%d",&t); while(t--) { ll A,B,a,b,ans; scanf("%lld %lld %lld %lld",&A,&B,&a,&b); if(ql(a,b,B-A,ans)) { ll tt; ql(a,b+a,B-A,tt); ans=min(ans,tt); ql(b,b+a,B-A,tt); ans=min(ans,tt); printf("%lld\n",ans); } else puts("-1"); } return 0; }
小结:对于扩展欧几里得算法,我觉得大都是套路,不太实用,比赛也基本很少出现这样的题。可能会出现一下需要逆元的情况,但是只作为一个优化而已,还有就是要警惕约瑟夫环与同余方程组一起出题。
做完这十七道题,我只能说我的数论终于入门了,后面的东西才是真正重要的东西,高斯消元、莫比乌斯反演、伯利亚定理、欧拉函数等等。下周、下下周,应该会陆续完成专题训练。