卢卡斯定理:
求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写吧,结果谁知道,完全有另一种更简单的方法,(已在代码中标出)忽然想起老师说的那句话,一个代码已经发现很多错,并不代表剩下的错很少,相反,剩下的错很多很多。
(最近迷上了学校里的胡辣汤泡油条,非常好吃)
2015.10.30:
java好还是c++还,来来来,大家来打一架!
(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));
}
}