世界真的很大
今天考试的第一题,讲道理拓展Lucas算NOIP?
还好学会了
考试的时候一看,思路和解法都非常简单,简单到已经不是这道题的主要部分了,求几个组合数就完了,模拟了一下样例过了,很开心
然后一看,嗯?mod怎么是一个合数呢?
EXLucas
看题先:
description:
一年一度的圣诞节快要来到了。每年的圣诞节小E都会收到许多礼物,当然他也会送出许多礼物。不同的人物在小E心目中的重要性不同,在小E心中分量越重的人,收到的礼物会越多。小E从商店中购买了n件礼物,打算送给m个人,其中送给第i个人礼物数量为wi。请你帮忙计算出送礼物的方案数(两个方案被认为是不同的,当且仅当存在某个人在这两种方案中收到的礼物不同)。由于方案数可能会很大,你只需要输出模P后的结果。
input:
输入的第一行包含一个正整数P,表示模;第二行包含两个整整数n和m,分别表示小E从商店购买的礼物数和接受礼物的人数;以下m行每行仅包含一个正整数wi,表示小E要送给第i个人的礼物数量。
output:
若不存在可行方案,则输出“Impossible”,否则输出一个整数,表示模P后的方案数
首先一句话谈谈题解,然后浅谈一下拓展卢卡斯
考虑一共要用SUM个礼物,如果比n大的话肯定就是Impossible
不到n的话呢,先枚举从n个里面选SUM个,然后从SUM里面选w1个,从SUM-w1里面选w2个,从SUM-w1-w2里面选w3个,以此类推
写出来就是C(n,SUM) * C(SUM,w1) * C(SUM-w1,w2) 。。。。
而且m只有5,感觉太少了一点,但是的确是这样
问题就出在这个组合数是一个合数。
Lucas定理都是出在mod为质数的情况下。
拓展lucas应运而生
现在来谈一谈拓展Lucas
我们要求的是C(n,m) % mod,mod是一个合数
其实合数的问题主要出在哪里呢?
即求的是 n! / m!/ (n-m)!
如果不是合数,我们采用的方法是处理出m!和(n-m)!的逆元
但是逆元必须要在与mod数互质的情况下才有
对于这一点,我们的处理方法是,把合数mod拆分成如下形式:
P1^a1 * P2^a2 * P3^a3 …………..
其中pi都是质数
考虑对于P1^a1 , P2^a2。。。。他们都是互质的。
考虑对于每一个Pi^ai作为mod数求出C(n,m)的值,就可以用中国剩余定理即孙子定理求出来最后的值
那么现在的问题就变成了求出每一个C(n,m) % Pi^ai的值
即n! / m!/ (n-m)! % Pi^ai
由于我们无法保证n!,m!,(n-m)!与Pi是互质的,所以暂时还是无法使用逆元
既然如此,我们由于Pi^ai的质因数只有有1个Pi,我们就考虑把这阶乘中的Pi全部提出来,变成x * Pi^y的形式
然后x与Pi互质,就可以使用逆元处理,Pi^y在分子和分母上可以通过指数的加加减减得到
即把原式处理成 :
x1 * Pi^y1 / (x2 * Pi^y2 x3 Pi^y3)的形式
那么现在问题就在于如何把n!处理成x * Pi^y的形式
举个例子,20!在mod3^2的意义下处理成 x * 3 ^y
首先先把20!里面含有3的提出来,如3,6,9,12,15,18,一共有6项
提出3^6,那么还剩下6! * 1 * 2 * 4* 5 *7 *8 *10 *11 *13 *14 *16 *17 *19 *20
对于这些数,在mod 3^2的意义下,就存在有循环节,1-9,10-18
每一个循环节乘起来mod 9 的值是相同的,算出一个的值然后快速幂就可以得到
然后还剩下的19和20就暴力算出
6!就变成了20!的子问题,可以递归调用函数求解
中国剩余定理就不讲了,应该都会把,简单版本的
完整代码:
#include<stdio.h>
typedef long long dnt;
dnt mod,n,w[200010],SUM=0;
int m;
dnt quickmub(dnt a,dnt b,dnt m)
{
dnt rt=1;
while(b)
{
if(b&1) rt=rt*a%mod;
a=a*a%mod,b>>=1;
}
return rt;
}
dnt exgcd( dnt a, dnt b, dnt &x, dnt &y )
{
if(b==0)
{
x=1,y=0;
return a;
}
dnt x0,y0;
dnt cd=exgcd(b,a%b,x0,y0);
x=y0;
y=x0-(a/b)*y0;
return cd;
}
dnt inverse( dnt a ,dnt m)
{
dnt x, y;
exgcd(a,m,x,y);
return (x%m+m)%m;
}
dnt Divide(dnt x,dnt prm,dnt prk)
{
if(x==0) return 1;
dnt tmp=1;
for(int i=1;i<=prk;i++)
if(i%prm) tmp*=i,tmp%=prk;
tmp=quickmub(tmp,x/prk,prk);
dnt tmp2=x%prk;
for(int i=2;i<=tmp2;i++)
if(i%prm) tmp*=i,tmp%=prk;
tmp=tmp*Divide(x/prm,prm,prk)%prk;
return tmp%prk;
}
dnt Misaka(dnt a,dnt b,dnt prm,dnt prk)
{
dnt tmp1=Divide(a,prm,prk);
dnt tmp2=Divide(b,prm,prk);
dnt tmp3=Divide(a-b,prm,prk);
dnt tmp=a,tot=0;
while(tmp) tot+=tmp/prm,tmp/=prm;
tmp=b;
while(tmp) tot-=tmp/prm,tmp/=prm;
tmp=a-b;
while(tmp) tot-=tmp/prm,tmp/=prm;
dnt bns=tmp1*inverse(tmp2,prk) %prk*inverse(tmp3,prk)%prk;
dnt cns=bns*quickmub(prm,tot,prk)%prk;
return cns*(mod/prk)%mod*inverse(mod/prk,prk)%mod;
}
dnt CRT(dnt a,dnt b)
{
dnt tmp=mod,rt=0;
for(int i=2;i<=tmp;i++)
{
dnt lt=1;
while(tmp%i==0) tmp/=i,lt*=i;
if(lt!=1) rt=(rt+Misaka(a,b,i,lt))%mod;
}
return rt%mod;
}
int main()
{
scanf("%lld",&mod);
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%lld",&w[i]),SUM+=w[i];
if(SUM>n)
{
printf("Impossible\n");
return 0;
}
}
dnt ans=CRT(n,SUM)%mod;
for(int i=1;i<=m;i++)
ans=ans*CRT(SUM,w[i])%mod,SUM-=w[i];
printf("%lld\n",ans);
return 0;
}
/*
EL PSY CONGROO
*/
嗯,就是这样