bzoj 2142 礼物——扩展lucas模板

本文针对一个具体的算法竞赛题目,介绍了如何运用扩展Lucas定理解决涉及大数组合的问题。文章通过实例代码展示了扩展Lucas定理的具体实现过程,并分享了作者在实现过程中的思考与体会。

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2142

没给P的范围,但说 pi ^ ci<=1e5,一看就是扩展lucas。

学习材料:https://blog.youkuaiyun.com/clove_unique/article/details/54571216

     https://www.cnblogs.com/elpsycongroo/p/7620197.html

于是打(抄)了第一份exlucas的板子。那个把 pi的倍数 和 其余部分 分开处理的写法非常清楚!自己本来还想弄个pair的函数什么的。

num的范围?

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int N=10005;
int num,m[N],pk[N];
ll mod,w[10],a[N],x,y,ans,n,l;
void init(ll n)
{
  for(ll i=2;i*i<=n;i++)
    if(n%i==0)
      {
    m[++num]=i;pk[num]=1;
    while(n%i==0)n/=i,pk[num]*=i;
      }
  if(n>1)m[++num]=n,pk[num]=n;
}
ll pw(ll x,ll k,int mod)
{
  ll ret=1;x%=mod;while(k){if(k&1)(ret*=x)%=mod;(x*=x)%=mod;k>>=1;}return ret;
}
ll multi(ll n,int pi,int pk)
{
  if(!n)return 1;//
  ll sum=1;
  for(int i=2;i<pk;i++)if(i%pi)(sum*=i)%=pk;
  sum=pw(sum,n/pk,pk);
  for(int i=2;i<=n%pk;i++)if(i%pi)(sum*=i)%=pk;
  return sum*multi(n/pi,pi,pk)%pk;//n/pi!!
}
void exgcd(ll a,ll b,ll &x,ll &y)
{
  if(!b){x=1;y=0;return;}
  exgcd(b,a%b,y,x);y-=a/b*x;
}
ll inv(ll n,ll mod){exgcd(n,mod,x,y);return (x+mod)%mod;}
ll exlucas(ll n,ll m,int pi,int pk)
{
  if(n<m)return 0;//
  ll a=multi(n,pi,pk),b=multi(m,pi,pk),c=multi(n-m,pi,pk);
  ll k=0;
  for(ll i=n;i;i/=pi)k+=i/pi;//阶乘的pi的个数 
  for(ll i=m;i;i/=pi)k-=i/pi;
  for(ll i=n-m;i;i/=pi)k-=i/pi;
  return a*inv(b,pk)%pk*inv(c,pk)%pk*pw(pi,k,pk)%pk;
}
ll crt()
{
  ll M=1,ret=0;for(int i=1;i<=num;i++)M*=pk[i];//pk,not m(pi)
  for(int i=1;i<=num;i++)
    {
      ll w=M/pk[i];
      (ret+=w*inv(w,pk[i])*a[i])%=mod;
    }
  return (ret+mod)%mod;
}
ll excomb(ll n,ll k)
{
  for(int i=1;i<=num;i++)
    a[i]=exlucas(n,k,m[i],pk[i]);
  return crt();
}
int main()
{
  scanf("%lld%lld%lld",&mod,&n,&l);ll tmp=0;
  init(mod);
  for(int i=1;i<=l;i++)scanf("%lld",&w[i]),tmp+=w[i];
  if(n<tmp){printf("Impossible");return 0;}
  ans=1;
  for(int i=1;i<=l;i++)
    {
      tmp=excomb(n,w[i]);
      (ans*=tmp)%=mod;n-=w[i];
    }
  printf("%lld\n",ans);
  return 0;
}

 

转载于:https://www.cnblogs.com/Narh/p/9263662.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值