hdu 5446
卢卡斯定理:
求c(n,m)%p(p是一个素数),设n=a[x]*p^x+a[x-1]*p^(x-1)+……+a[0]*p^0,m=b[x]*p^x+b[x-1]*p^(x-1)+……+b[0]*p^0,(如果m化成p进制,不够x+1位,就补0),则c(n,m)=c(a0,b0)*c(a1,b1)*……*c(ax,bx) (%p).
证:
首先(1+x)^(p^z)=1+x^(p^z) (%p),其次f(x)=g(x) (%p),说明f(x)中x^y的系数模p等于g(x)中x^y的系数模p
那么 (1+x)^n=(1+x)^(a[x]*p^x+a[x-1]*p^(x-1)+……+a[0]*p^0) (%p)
=(1+x)^(a[x]*p^x)*(1+x)^(a[x-1]*p^(x-1))*……*(1+x)^(a[0]*p^0) (%p)
=(1+x^(p^x))^a[x]*(1+x^(p^(x-1)))^a[x-1]*……*(1+x^(p^0))^a[0] (%p)
左边x^m的系数是c(n,m),因为一个数的p进制只有一种形式,所以右边x^m的系数是c(ax,bx)*c(a(x-1),b(x-1))*……*c(a0,b0),他们%p的余数相等。
中国剩余定理:
互质版:
主要是构造一个通解:
x%p1=a1
x%p2=a2
……
x%py=ay
m=p1*p2*……py
mi=m/pi; mj
设mii是mi关于pi的逆元,所以构造
x=a1*m1*m11+a2*m2*m22+a3*m3*m33+……+ay*my*myy
非互质版:
参考链接:http://yzmduncan.iteye.com/blog/1323599
注:(n1/d)^(-1)代表逆元
先逆着验证:
x=k'*n1*n2/d+n1*(a2-a1)/d*(n1/d)^(-1)+a1;
当x%n1时,显然是a1,
当x%n2时, 因为(n1/d)^(-1)是关于(n2/d)的,所以(n1/d)*n1=k*n2+d,带入得 k*n1*n2/d+(a2-a1)/d*(k*n2+d)+a1,它模上n2时,得到a2.
注:(a*b)%c=((a%c)*(b%c))%c,成立,当且仅当a,b均为整数。
链接里是正着推的,大概思路就是把两个式子
x=k1*n1+a1
x=k2*n2+a2
合成一个式子,然后解这个式子,求出k1,再代入第一个式子。
其实求得过程中也可以一直%n2,也就是说 k1=(a2-a1)*(n1^-1) %n2,最后再代入,但是因为n1关于n2的逆元不一定存在,所以化成求n1/d关于n2/d的逆元,这样就保证逆元一定是存在的。
注:最后转化成x%(n1*n2/d)的值,其实可以一直是x%(n2/d)到最后,为什么要转化呢?因为x所有的可能解模(n1*n2/d)相等,假设等于y,而y+k*(n1*n2/d)都是x的可能解,所以最后要转化成x%(n1*n2/d),虽然x的可能解模上(n2/d)相等,假设等于z,但是z+k(n2/d) 不都是x的可能解。
所以整体大概就是这样一个思路,反反复复看了好几次,每一次都是无疾而终,感觉看倒是顺的下来,就是不知道它在做些什么,为什么要这样做。,这次感觉窥探到了一丝奥秘。
假设p=p1*p2*……*pk
对于为什么可以用中国剩余定理呢?假设通过中国剩余定理求出来某个具体答案,那么这个答案加上k倍的p(k属于整数)就是这个x(含义见中国剩余定理中x的含义)的所有可取值,最后我们要求的这个组合数是模上p的,所以在[0,p)之间x只有一个取值,所以就是那个,没有疑问。
代码参考:http://blog.sina.com.cn/s/blog_12fea76590102w6ts.html
用java写的超时代码,但是估计不只是超时,还有wr,但是不能扔呀,里面全是错误经验。本来觉得有大数,就用java写吧,结果谁知道,完全有另一种更简单的方法,(已在代码中标出)忽然想起老师说的那句话,一个代码已经发现很多错,并不代表剩下的错很少,相反,剩下的错很多很多。
(最近迷上了学校里的胡辣汤泡油条,非常好吃)
(java,不ac)
import java.math.BigInteger; import java.util.Scanner; public class Main{ static Scanner in=new Scanner(System.in); static int N=20; static long[] a=new long[N]; static long[] b=new long[N]; static long[] ar=new long[2]; /*static long inv(long x,long y){ BigInteger ans=new BigInteger("1"); BigInteger yy=new BigInteger(String.valueOf(y)); long tempy=y-2; BigInteger tempx=new BigInteger(String.valueOf(x)); while(tempy!=0){ if(tempy%2!=0){ ans=ans.multiply(tempx).mod(yy); } tempy=tempy/2; tempx=tempx.multiply(tempx).mod(yy); } return ans.longValue(); }*/ static int inv(int x,long y){ long ans=1; long tempy=y-2; long tempx=x; while(tempy!=0){ if(tempy%2!=0){ ans=ans*tempx%y; } tempy=tempy/2; tempx=tempx*tempx%y; } return (int)ans; } static long lukasi(long n,long m,long p){ if(n<p&&m<p){ if(m>n){ return (long)0;//加了一个强制转化 } else if(m==n){ return (long)1;//加了一个强制转化 } else if(m==0){ return (long)1; } else{ long ans=1; for(int i=1;i<=m;i++){//应该是<=m,而不是<m ans=ans*inv(i,p)%p*(n-i+1)%p; } return ans; } } else{ return lukasi(n/p,m/p,p)*lukasi(n%p,m%p,p)%p; } } /*static void merge(int x,int y){ long c=b[y]-b[x]; long d=1; long n1=a[x],n2=a[y],a1=b[x],a2=b[y]; c=(c%n2+n2)%n2;//这里模数写成了2 c=c/d; n1=n1/d; n2=n2/d; c=c*inv(n1,n2); c=c%n2; BigInteger tempc= new BigInteger(String.valueOf(c)); a[y]=(new BigInteger(String.valueOf(n1))).multiply(new BigInteger(String.valueOf(n2))).longValue(); tempc=tempc.multiply(new BigInteger(String.valueOf(n1))).add(new BigInteger(String.valueOf(a1))).mod(new BigInteger(String.valueOf(a[y]))); b[y]=tempc.longValue(); return; }*/ static void exgcd(/*BigInteger[] arlong[] ar,*/long x,long y){ if(y==0){ //ar[0]=new BigInteger("1"); //ar[1]=new BigInteger("1"); ar[0]=1;//为什么这里不需要大数? ar[1]=1; } else{ //BigInteger[] br =new BigInteger[2]; long[] br=new long[2]; exgcd(/*br,*/y,x%y); br[0]=ar[1]; //ar[1]=br[0].subtract((new BigInteger(String.valueOf(x/y)).multiply(br[1]))); br[1]=ar[0]-x/y*ar[1]; ar[0]=br[0]; ar[1]=br[1]; } return; } static long mult(long x,long y,long p){ long ans=0; while(y!=0){ if((y%2)!=0){ ans=(ans+x)%p; } y=y/2; x=(x+x)%p; } return ans; } static long query(long n,long m,int k){ for(int i=0;i<k;i++){ b[i]=lukasi(n,m,a[i]); } /*for(int i=0;i<k-1;i++){ //System.out.println(a[i]+" "+b[i]+" "+a[i+1]+" "+b[i+1]); merge(i,i+1); } //System.out.println(b[k-1]); return b[k-1];*/ long q=1; for(int i=0;i<k;i++){ q*=a[i]; } long ans=0;//因为加法不需要大数,所以把ans从BigInteger换回long BigInteger modnum=new BigInteger(String.valueOf(q)); for(int i=0;i<k;i++){ //BigInteger[] ar=new BigInteger[2];//这里要new,不要null //long[] ar=new long[2]; //BigInteger temp=new BigInteger(String.valueOf(q/a[i])); exgcd(/*ar,*/q/a[i],a[i]);//这里没有返回 //temp=temp.multiply(new BigInteger(String.valueOf(b[i]))); //temp=temp.multiply(new BigInteger(String.valueOf(ar[0]))).mod(modnum); //ans=(ans+temp.longValue())%q;//把q弄成BigInteger,这样就不用每次都转化了,果真有tle变成了wr //不用大数,会tle ar[0]=(ar[0]%q+q)%q;//ar[0]在这里应该做这样的处理,否则不能保证ar[0]的范围 //System.out.println(ar[0]+" "+q+" "+a[i]+" "+b[i]); ans=(mult(mult(q/a[i],b[i],q),ar[0],q)+ans)%q;//加法不会溢出,因为10^18+10^18<2^64 //System.out.println(ans); } return ans; } static public void main(String args[]){ int t; long n,m; int k; t=in.nextInt(); while((t--)!=0){ n=in.nextLong(); m=in.nextLong(); k=in.nextInt(); for(int i=0;i<k;i++){ a[i]=in.nextLong(); } System.out.println(query(n,m,k)); } } }
(c++,已ac)
#include<stdio.h> #include<string.h> #include<iostream> using namespace std; #define N 20 long long int a[N],b[N]; void exgcd(long long int *x,long long int *y,long long int anum,long long int bnum){ if(bnum==0){ *x=1; *y=0; } else{ long long int tempx,tempy; exgcd(&tempx,&tempy,bnum,anum%bnum); *x=tempy; *y=tempx-anum/bnum*tempy; } return; } long long int inv(int x,long long int y){ long long int ans=1,tempx=x,tempy=y-2; while(tempy){ if(tempy%2){//这里把tempy写成了tempx,所以wr了 ans=ans*tempx%y; } tempy=tempy/2; tempx=tempx*tempx%y; } return ans; } long long int lukasi(long long int n,long long int m,long long int q){ if(n<q&&m<q){ if(m==0||m==n){ return 1; } else if(m>n){ return 0; } else if(m==1){ return n; } else{ long long int ans=1; for(int i=1;i<=m;i++){ ans=ans*inv(i,q)%q*(n-i+1)%q; } return ans;//忘了写return,请带脑袋好吗? } } else{ return lukasi(n/q,m/q,q)*lukasi(n%q,m%q,q)%q; } } long long int mult(long long int x,long long int y,long long int q){//这道题get到的点在这里 long long int ans=0; while(y){ if(y%2){ ans=(ans+x)%q;//这里把ans写成了y,写代码时请带脑袋! } y=y/2; x=(x+x)%q; } return ans; } long long int query(long long int n,long long int m,int k){ for(int i=0;i<k;i++){ b[i]=lukasi(n,m,a[i]); } long long int q=1; for(int i=0;i<k;i++){ q=q*a[i]; } long long int ans=0;//加法初始化是0,乘法初始化才是1 for(int i=0;i<k;i++){ long long int x,y; exgcd(&x,&y,q/a[i],a[i]); //printf("%lld %lld %lld %lld\n",q,a[i],b[i],x); x=(x%q+q)%q; ans=(ans+mult(mult(q/a[i],b[i],q),x,q))%q;//printf("wo shi da hao ren\n");//调试:这里没有回来 //printf("%lld\n",ans); } return ans; } int main(){ int t; long long int n,m; int k; scanf("%d",&t); while(t--){ scanf("%lld%lld%d",&n,&m,&k); for(int i=0;i<k;i++){ scanf("%lld",&a[i]); } printf("%lld\n",query(n,m,k)); } }