【jzoj3214】【SDOI2013】【方程】【中国剩余定理】【组合数取模】

56 篇文章 0 订阅

题目大意

给定方程
X1+X 2+…+Xn=m
我们对第 1.. n1 个变量 进行一些限制 :
X1≤A1
X2≤A2
Xn1 ≤An1
我们对第 n1+1.. n1+1.. n1+ n2 个变量 进行一些限制 :
X_(n1+1)≥A_(n1+1)
X_(n1+2)≥A_(n1+2)
X_(n1+n2) ≥A_(n1+n2)
求:在满足这些限制的前提下, 该方程正整数解的个数。
答案可能很大,请输出对 p取模 后的答案 ,也即 答案除以 p的余数。

解题思路

观察可知,没有限制可以用隔板原理组合数解决,后面的限制可以强制分配满足限制。前面的限制可以用容斥原理解决。
关键是这题组合数n,m很大,模数不是质数。这样我们就先把模数分解质因数,对每种质因数分别求模后的组合数,再用中国剩余定理组合答案。
由于组合数要用逆元,对于单独质因子p^x,我们来求阶乘,可以考虑把质因子抽出来考虑,其他的再考虑。考虑将p的倍数先抽出来,前一部分有循环节,后一部分可以递归求解。
19!%9=(12345678910111213141516171819)%9
=(12457810111314161719)36(123456)%9
这样阶乘就由两部分组成,不含因子的可以逆元,含因子的直接加减。这样成功求出了对小模数取模的组合数。
我们用中国剩余定理合并,我们要求一个数满足所有对小模数取模得到取模后的数。对于每一个小模数,求一个数对其取模得到想要的答案,对其他小模数取模等于零,我们考虑那个数是想要的数乘以一,但是这个一是由所有除当前模数的模数积乘以它的逆元构成的,由于前者包含的因子包含了其他模数所以取模后为零。将所有这些数加起来就是所要求的答案。

code

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LF double
#define LL long long
#define min(a,b) ((a<b)?a:b)
#define max(a,b) ((a>b)?a:b)
#define fo(i,j,k) for(int i=j;i<=k;i++)
#define fd(i,j,k) for(int i=j;i>=k;i--)
using namespace std;
int const MxN=1e7;
int N,M,Mo,a[10],Pri[10],Phi[10],Cnt[10],PC[10];LL F[10][10007+10];
int Pow(LL X,int Y,int Mo){
    LL Ans=1;
    while(Y){
        if(Y&1)Ans=Ans*X%Mo;
        X=X*X%Mo;
        Y>>=1;
    }
    return Ans;
}
int FF(int i,int P){
    F[i][P]=1;
    fo(j,1,P)if(j%Pri[i])F[i][P]=F[i][P]*j%PC[i];
    return F[i][P];
}
int NP;
int Fact(int N,LL &A,LL &B,int i){
    A=Pow((F[i][PC[i]])?F[i][PC[i]]:FF(i,PC[i]),(N/PC[i]),PC[i]);B=N/Pri[i];
    NP=N%PC[i];A=A*((F[i][NP])?F[i][NP]:FF(i,NP))%PC[i];
    LL AA=1,BB=0;if(N/Pri[i])Fact(N/Pri[i],AA,BB,i);
    A=A*AA%PC[i];B+=BB;
}
LL A,B,AA,BB;
int CC(int N,int M,int i){
    if(N<M)return 0;
    Fact(N,A,B,i);
    Fact(M,AA,BB,i);
    A=A*Pow(AA,Phi[i]-1,PC[i])%PC[i];B-=BB;
    Fact(N-M,AA,BB,i);
    A=A*Pow(AA,Phi[i]-1,PC[i])%PC[i];B-=BB;
    return (B<Cnt[i])?A*Pow(Pri[i],B,PC[i])%PC[i]:0;
}
int Ans;
int C(int N,int M){
    Ans=0;
    fo(i,1,Pri[0])Ans=(Ans+1ll*Pow(Mo/PC[i],Phi[i],Mo)*CC(N,M,i))%Mo;
    return Ans;
}
int main(){
    freopen("d.in","r",stdin);
    freopen("d.out","w",stdout);
    int T;scanf("%d%d",&T,&Mo);
    int MM=Mo,Mx=sqrt(MM);
    fo(i,2,Mx)if(MM%i==0){
        Pri[++Pri[0]]=i;Cnt[Pri[0]]++;Phi[Pri[0]]=i-1;MM/=i;
        while(MM%i==0)Cnt[Pri[0]]++,Phi[Pri[0]]*=i,MM/=i;
        PC[Pri[0]]=pow(Pri[Pri[0]],Cnt[Pri[0]]);
    }
    if(MM!=1)Pri[++Pri[0]]=MM,Cnt[Pri[0]]++,Phi[Pri[0]]=MM-1,PC[Pri[0]]=pow(Pri[Pri[0]],Cnt[Pri[0]]);
    int N1,N2,X,S,tag,Ans;
    fo(cas,1,T){
        scanf("%d%d%d%d",&N,&N1,&N2,&M);
        fo(i,1,N1)scanf("%d",&a[i]);
        fo(i,1,N2){scanf("%d",&X);M-=max(X-1,0);}
        Mx=(1<<N1)-1;Ans=0;
        fo(s,0,Mx){
            S=s,MM=M,tag=1;
            fo(i,1,N1){
                if(S&1)MM-=a[i],tag=-tag;
                S>>=1;
            }
            Ans=(Ans+tag*C(MM-1,N-1))%Mo;
        }
        printf("%d\n",(Ans+Mo)%Mo);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值