51nod1352 集合计数(扩展欧几里得)
题目链接:http://www.51nod.com/Challenge/Problem.html#!#problemId=1352
思路:题目就是求ax+by=n+1,有几个符合的(x,y)。
首先找到a,b的最大公约数d,n+1是d的倍数才有符合的解。
我们可以简单证明下:因为d是a,b的最大公约数,设a=k1*d,b=k2*d(k1,k2为正整数).
所以a*(k1*d)+b*(k2*d)=n+1,即(n+1)/d=a*k1+b*k2,所以n+1一定要是最大公约数的倍数才有解
先用算出a*x+b*y=n+1 的最小正数x解,如果此时y不是正数则没有解答案是0,如果是正数则答案是y/(a/gcd(a,b))(+1)
(如果y是a/gcd(a,b)的倍数不加1,不是就加1,可以这样理解y本身是一个解,如果整除,y的解包含进去了,不整除,y这个解没加)
也可以上下同时乘以b, 写成b*y/(a*b/gcd(a,b))又因为a*b/gcd(a,b)就是a,b的最小公倍数,所以又可以写成b*y/lcm(a,b)(+1)(lcm(a,b)的最小公倍数)
为什么答案是y/(a/gcd(a,b)(+1)呢?
因为(x,y)是一组解,则(x+k*b/gcd(a,b),y-k*a/gcd(a,b))(k为正整数)也是解。自己可以动手写就知道了
为什么是除以a/gcd(a,b)不是除以a呢?因为是除a解多还是除a/gcd(a,b)多?我也想了半天才知道。。。。笑笑自己弱鸡
哪最小正数解x怎么算呢?
我们只要用扩展欧几里得模板算出a*x+b*y=gcd(a,b) 的一个解x 再乘以(n+1)/gcd(a,b)就可以了得出a*x+b*y=n+1的一个x解,
但是此时x有可能不是最小,也有可能是负数,怎么办?
由上面我们可以知道a*(x+b/gcd(a,b))+b*(y-a/gcd(a,b))=n+1 每b/gcd(a,b)有一个x解,只要x对b/gcd(a,b)取模就行了,
再判断是负数就加b/gcd(a,b)就是最小正数解了。
代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> using namespace std; typedef long long ll; ll exgcd(ll a,ll b,ll &x,ll &y) { if(b==0) { x=1; y=0; return a; } ll ans=exgcd(b,a%b,x,y); ll t=x; x=y; y=t-(a/b)*y; return ans; } int main() { ll t; cin>>t; ll n,a,b; while(t--) { cin>>n>>a>>b; ll x,y; ll d=exgcd(a,b,x,y);//a,b的最大公约数 //cout<<x<<" "<<y<<endl; ll lcm=a*b/d; if((n+1)%d) cout<<"0"<<endl; else { x=x*(1+n)/d; ll r=b/d; x=x%r; while(x<=0) x+=r;//最小的正整数解x ll s=n+1-x*a;//s为b*y if(s<0) cout<<"0"<<endl; else { if(s%a) cout<<1+s/lcm<<endl; else cout<<s/lcm<<endl; } } } }