zoukankan      html  css  js  c++  java
  • 【BZOJ2142】礼物 组合数+CRT

    【BZOJ2142】礼物

    Description

    小E从商店中购买了n件礼物,打算送给m个人,其中送给第i个人礼物数量为wi。请你帮忙计算出送礼物的方案数(两个方案被认为是不同的,当且仅当存在某个人在这两种方案中收到的礼物不同)。由于方案数可能会很大,你只需要输出模P后的结果。

    Input

    输入的第一行包含一个正整数P,表示模;
    第二行包含两个整整数n和m,分别表示小E从商店购买的礼物数和接受礼物的人数;
    以下m行每行仅包含一个正整数wi,表示小E要送给第i个人的礼物数量。

    Output

    若不存在可行方案,则输出“Impossible”,否则输出一个整数,表示模P后的方案数。

    Sample Input

    100
    4 2
    1
    2

    Sample Output

    12
    【样例说明】
    下面是对样例1的说明。
    以“/”分割,“/”前后分别表示送给第一个人和第二个人的礼物编号。12种方案详情如下:
    1/23 1/24 1/34
    2/13 2/14 2/34
    3/12 3/14 3/24
    4/12 4/13 4/23
    【数据规模和约定】
    设P=p1^c1 * p2^c2 * p3^c3 * … *pt ^ ct,pi为质数。
    对于100%的数据,1≤n≤109,1≤m≤5,1≤pi^ci≤10^5。

    题解:答案很简单,$ans=C_{tot}^{w1}C_{tot-w1}^{w2}C_{tot-w1-w2}^{w3}...$。

    但是问题来了,首先模数既不是质数,也不是若干独立质数的乘积,而是质数的幂次之积。我们还是先用中国剩余定理,将模数变成质数的幂次,但此时普通的lucas定理无法使用,我们怎么求组合数呢?

    我们考虑将组合数变成阶乘相除的形式,再将分子和分母中的阶乘都化成$a imes p^b$的形式(a对$p^c$取模),最后答案的a等于分子的a乘上分母的a的逆元,答案的b等于分子的b-分母的b。那么我们考虑如何将一个数的阶乘化成$a imes p^b$的形式。

    以$p=5,p^c=25$为例,然后将n的阶乘中5的倍数都提出来考虑:

    $n!=(1 imes2 imes3 imes4 imes6 imes7 imes...) imes5^{lfloorfrac n 5 floor} imes(lfloorfrac n 5 floor)!$

    那么如何处理前面的那坨东西呢?我们可以先预处理出$1...24$的阶乘(不计算p的倍数),即为jc,然后前面的那堆东西就变成了$jc[24]^{lfloor frac n {25} floor} imes jc[n\%25]$。

    对于后面的那个阶乘,我们可以递归处理下去,每次n的大小会除以p,所以复杂度是可以接受的~

    最后用CRT合并,求逆元时用exgcd即可,注意判Impossible的情况。

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    using namespace std;
    typedef long long ll;
    const int maxn=100010;
    ll n,m,num,P,PP,PC,Pri,ans;
    ll w[10],jc[maxn];
    inline ll pm(ll x,ll y)
    {
    	ll ret=1;
    	while(y)
    	{
    		if(y&1)	ret=ret*x%PP;
    		x=x*x%PP,y>>=1;
    	}
    	return ret;
    }
    void exgcd(ll a,ll b,ll &x,ll &y)
    {
    	if(!b)
    	{
    		x=1,y=0;
    		return ;
    	}
    	exgcd(b,a%b,x,y);
    	ll t=x;
    	x=y,y=t-a/b*x;
    }
    inline ll ine(ll a)
    {
    	ll x,y;
    	exgcd(a,PP,x,y);
    	return x;
    }
    struct node
    {
    	ll x,y;
    	node() {x=1,y=0;}
    	node(ll a,ll b) {x=a,y=b;}
    	node operator + (const node &a) const {return node(x*a.x%PP,y+a.y);}
    	node operator * (const ll &a) const {return node(pm(x,a),y*a);}
    	node operator - (const node &a) const {return node(x*(ine(a.x)+PP)%PP,y-a.y);}
    };
    inline node getjc(ll x)
    {
    	if(x<P)	return node(jc[x],0);
    	return node(pm(jc[PP-1],x/PP)*jc[x%PP]%PP,x/P)+getjc(x/P);
    }
    inline ll solve()
    {
    	ll i,sum=n;
    	for(jc[0]=1,i=1;i<PP;i++)
    	{
    		jc[i]=jc[i-1];
    		if(i%P)	jc[i]=jc[i]*i%PP;
    	}
    	node a,b;
    	for(i=1;i<=m;i++)	b=b+getjc(w[i]),sum-=w[i];
    	a=getjc(n),b=b+getjc(sum);
    	a=a-b;
    	return a.x*pm(P,a.y);
    }
    inline ll CRT()
    {
    	ll i,t=Pri;
    	for(i=2;i*i<=t;i++)
    	{
    		if(t%i==0)
    		{
    			P=i,PP=1,PC=0;
    			while(t%i==0)	t/=i,PP*=i,PC++;
    			ans=(ans+ine(Pri/PP)*solve()*(Pri/PP))%Pri;
    		}
    	}
    	if(t!=1)
    	{
    		P=PP=t,PC=1;
    		ans=(ans+ine(Pri/PP)*solve()*(Pri/PP))%Pri;
    	}
    	return (ans+Pri)%Pri;
    }
    int main()
    {
    	scanf("%lld%lld%lld",&Pri,&n,&m);
    	ll tmp=0;
    	for(ll i=1;i<=m;i++)	scanf("%lld",&w[i]),tmp+=w[i];
    	if(tmp>n)
    	{
    		printf("Impossible");
    		return 0;
    	}
    	printf("%lld",CRT());
    	return 0;
    }
  • 相关阅读:
    WPF中实现Button.Content变化的简易动画
    WPF中ListBox的项ListBoxItem被选中的时候Background变化
    GD库处理图像
    PHP上传文件示例
    会话控制:Cookie和session
    PHP表单与验证
    PHP日期与时间
    PHP正则表达式
    PHP字符串处理
    目录与文件操作
  • 原文地址:https://www.cnblogs.com/CQzhangyu/p/7954771.html
Copyright © 2011-2022 走看看