把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷4640】[BJWC2008] 王之财宝(经典容斥)

点此看题面

  • 吉尔伽美什有\(n\)种宝具,其中有\(T\)种超级宝具有一定数量,其余无限。
  • 求有多少种方式选出\(m\)个宝具。
  • \(n,m\le10^9,T\le15\),模数\(\le10^5\)

经典容斥

看到\(T\le15\)这种范围,想想这种题目又不可能状压,于是自然而然就会想到容斥了。

我们去枚举一个集合\(S\),表示这个集合中的超级宝具一定超出数量,而其余超级宝具任意。

显然答案就应该是:(\(Calc(S)\)表示对应方案数)

\[\sum (-1)^{|S|}Calc(S) \]

那么问题就在于如何求出\(Calc(S)\)了。

插板法计算方案

考虑一下现在的问题,就是有若干超级宝具至少要选择\(a_i+1\)个,其余所有宝具都可以任选。

那么,如果我们对于这些强制要选的超级宝具先把这\(a_i+1\)个宝具都选掉,即令\(k=m-\sum_{i\in S}(a_i+1)\),问题就变成了所有宝具都可以任选,总共要选出不超过\(k\)个。

这个问题就非常简单了,直接插板法计算贡献,显然答案就是\(C_{k+n}^n\)

至于如何计算下标这么大的组合数,显然卢卡斯定理就好了。

代码:\(O(n2^T)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define T 15
#define P 100000
using namespace std;
int n,m,t,X,a[T+5],g[1<<T];
I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
int Fac[P+5],IFac[P+5];I void Init_Fac()//预处理阶乘和阶乘逆元
{
	RI i;for(Fac[0]=i=1;i^X;++i) Fac[i]=1LL*Fac[i-1]*i%X;
	for(IFac[X-1]=QP(Fac[X-1],X-2),i=X-1;i;--i) IFac[i-1]=1LL*IFac[i]*i%X;
}
I int C(CI x,CI y)//卢卡斯定理计算组合数
{
	return x>=y?(x>=X?1LL*C(x/X,y/X)*C(x%X,y%X)%X:1LL*Fac[x]*IFac[y]%X*IFac[x-y]%X):0;
}
I int Calc(CI s)//求强制集合s中的超级宝具超出限制的方案数
{
	RI i,k=m;for(i=1;i<=t;++i) s>>i-1&1&&(k-=a[i]+1);return C(k+n,n);//先选出必选部分,然后所有宝具都可任选
}
int main()
{
	RI i;for(scanf("%d%d%d%d",&n,&t,&m,&X),Init_Fac(),i=1;i<=t;++i) scanf("%d",a+i);
	RI res=0,l=1<<t;for(i=0;i^l;++i) res=(((g[i]=g[i>>1]^(i&1))?X-1LL:1LL)*Calc(i)+res)%X;//经典容斥
	return printf("%d\n",res),0;
}
posted @ 2021-02-09 12:31  TheLostWeak  阅读(92)  评论(0编辑  收藏  举报