ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【题解】组合数学

2021-12-16 17:34:39  阅读:266  来源: 互联网

标签:limits 组合 int 题解 rep 数学 binom sum define


来自\(\texttt{SharpnessV}\)的省选复习计划中的组合数学


由于作者非常菜所以只能随便写点基础的。


P3197 [HNOI2008]越狱

简单数数。越狱的方案数等于总方案数减没有越狱的方案数。

所以\(Ans=m^n-m\times (m-1)^{n-1}\) 。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 100005
#define P 100003
using namespace std;
typedef long long ll;
ll Pow(ll x,ll y){
	ll now=1;
	for(;y;y>>=1,x=x*x%P)if(y&1)now=now*x%P;
	return now;
}
int main(){
	ll m,n;scanf("%lld%lld",&m,&n);
	printf("%lld\n",(Pow(m,n)-m*Pow(m-1,n-1)%P+P)%P);
	return 0;
}

下面是组合数环节,以下是必背恒等式。

递推式

\[\binom{n}{m}=\binom{n-1}{m-1}+\binom {n-1}{m} \]

阶乘式

\[\binom{n}{m}=\dfrac{n!}{m!(n-m)!} \]

对称式

\[\binom{n}{m}=\binom{n}{n-m} \]

合并式

\[\binom{n}{k}\binom{k}{m}=\binom{n}{m}\binom{n-m}{k-m} \]

求和式 \(1\)

\[\sum\limits_{i=0}^n\binom{m+i}{i}=\binom{n+m+1}{n} \]

求和式 \(2\)

\[\sum\limits_{i=m}^n\binom{i}{m}=\binom{n+1}{m+1} \]

二项式定理

\[(a+b)^n=\sum\limits_{i=0}^n \binom{n}{i}a^ib^{n-i} \]

范德蒙德卷积

\[\sum\limits_{i=0}^{k}\binom{n}{i}\binom{m}{k-i}=\binom{n+m}{k} \]

奇偶和相等

\[\sum\limits_{i=2k}\binom{n}{i}=\sum\limits_{i=2k+1}\binom{n}{i} \]

除了等式,还需要知道二项式反演

\[f(n)=\sum\limits_{i=0}^{n}\binom{n}{i}g(i)\ \ \iff\ \ g(n)=\sum\limits_{i=0}^{n}(-1)^{n-i}\binom{n}{i}f(i) \]

卢卡斯定理

\[\binom{n}{m}\equiv \binom{\left\lfloor\frac{n}{p}\right\rfloor}{\left\lfloor\frac{m}{p}\right\rfloor}\binom{n\bmod p}{m\bmod p} \pmod{p} \]


P2822 [NOIP2016 提高组] 组合数问题

直接递推求组合数。时间复杂度\(\rm O(N^2)\),好处是不用担心 \(P\) 不是质数。

#include<bits/stdc++.h>
using namespace std;
int t,k;
int f[2005][2005],sum[2005][2005];
void prepare(){
	f[0][0]=1;
	for(int i=1;i<=2000;i++){
	  for(int j=1;j<=2000;j++)
	    f[i][j]=(f[i-1][j-1]+f[i-1][j])%k;
	  f[i][0]=1;
	}
	for(int i=0;i<=2000;i++)
	  for(int j=0;j<=2000;j++)
	    if(i>=j&&!f[i][j])
	      sum[i][j]++;
	for(int i=1;i<=2000;i++){
	  for(int j=1;j<=2000;j++)
	    sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
	}
}
int main()
{
	scanf("%d%d",&t,&k);
	prepare();
	while(t--){
		int x,y;scanf("%d%d",&x,&y);
		printf("%d\n",sum[x][y]);
	}
	return 0;
}

P2290 [HNOI2004]树的计数

\(\rm Prufer\) 序列,唯一需要知道的是一个长度为 \(n-2\) 的值域为 \(n\) 的序列与一颗有标号无根树对应,并且是一一对应关系。

并且度数为 \(d_i\) 的节点在序列中恰好出现 \(d_i-1\) 次。

所以 \(n\) 个节点的无根树个数是 \(n^{n-2}\) 。

这道题给定度数,就是可重集排列,直接套公式即可。

Code


P3807 【模板】卢卡斯定理

套用定理,预处理\(p\)以内的阶乘,对于\(n,m\ge p\)的组合数,利用定理递归下去即可。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 100005
using namespace std;
int n,m,P,fac[N],inv[N];
int Pow(int x,int y){
	int now=1;
	for(;y;y>>=1,x=1LL*x*x%P)if(y&1)now=1LL*now*x%P;
	return now;
}
int C(int n,int m){
	if(n<m||n<0||m<0)return 0;
	return 1LL*fac[n]*inv[m]*inv[n-m]%P;
}
int lucas(int n,int m){
	if(n<P&&m<P)return C(n,m);
	return 1LL*lucas(n/P,m/P)*lucas(n%P,m%P)%P;
}
int main(){
	int T;scanf("%d",&T);
	fac[0]=inv[0]=1;
	while(T--){
		scanf("%d%d%d",&n,&m,&P);
		rep(i,1,P-1)fac[i]=1LL*fac[i-1]*i%P,inv[i]=Pow(fac[i],P-2);
		printf("%d\n",lucas(n+m,n));
	}
	return 0;
}

P7044 「MCOI-03」括号

非常水的基础组合数学题。

首先我们对每个括号算贡献,简单容易一下得到一个括号的贡献为可重集组合。

这里的可重集组合是每个数都有无限个,从 \(n\) 种数种选择 \(m\) 个数的方案数。

我们假设第 \(i\) 次选的数是 \(a_i\),不失一般性我们令 \(a_i\) 不减。

令 \(k_i=a_i+i-1\) ,不难发现 \(k_i\) 递增,且 \(k_i\) 序列与 \(a_i\) 序列一一对应。

而 \(k_i\) 的取值范围是 \(n+m-1\),所以可重集组合数就是\(\binom{n+m-1}{m}\)。

#include<bits/stdc++.h>
#define int long long 
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 2000005
#define P 998244353
using namespace std;
typedef long long ll;
char s[N];int n,k,sta[N],top;ll fac[N<<1],cur[N],inv[N<<1],ans;
ll Pow(ll x,int y){ll now=1;for(;y;y>>=1,x=x*x%P)if(y&1)now=now*x%P;return now;}
ll C(int x,int y){return fac[x]*inv[y]%P*inv[x-y]%P;}
void init(){
	inv[0]=fac[0]=1;
	rep(i,1,n+k)fac[i]=fac[i-1]*i%P,inv[i]=Pow(fac[i],P-2);
	rep(i,1,n)cur[i]=C(i+k-1,k);
}
ll R(int x,int y){return (cur[x]-cur[x-y])%P;}
ll T(int x){return cur[x];}
signed main(){
	scanf("%lld%lld",&n,&k);
	scanf("%s",s+1),init();
	for(int i=1;i<=n;i++)if(s[i]=='(')sta[++top]=i;
		else ans+=1LL*R(i,i-sta[top])*T(n-i+1)%P,top=std::max(0LL,top-1);
	top=0;sta[0]=n+1;
	for(int i=n;i;i--)if(s[i]==')')sta[++top]=i;
		else ans+=T(i)*R(n-i+1,sta[top]-i)%P,top=std::max(top-1,0LL);
	printf("%lld\n",(ans%P+P)%P);
	return 0;
}

P4071 [SDOI2016]排列计数

错位排列问题,\(D_n\) 表示 \(n\) 个数的错排方案数。

我们对最后一个数 \(n\) ,它可以错开到 \(1\sim n-1\)。

假设它错开到 \(i\) ,那么如果第 \(n\) 位为 \(i\) ,方案数为\(D_{n-2}\),否则方案数为 \(D_{n-1}\)。

这样我们就得到了递推式\(D_n=(n-1)(D_{n-1}+D_{n-2})\),或者换一种写法\(D_n=nD_{n-1}+(-1)^n\)。

Code


P2480 [SDOI2010]古代猪文

给定\(n,g\),求 \(\large g^{\sum\limits_{d\mid n}\binom{n}{d}}\bmod 999911659\)

\(999911659\)是一个质数,根据费马小定理,我们直接求\(\sum\limits_{d\mid n}\binom{n}{d}\bmod 999911658\) 即可。

这个数显然不是质数,但是这个数可不是乱写的,\(999911658=2\times 3\times 4679\times 35617\),这又是四个小质数。

我们对每个质数用\(\rm Lucas\)求出\(\binom{n}{d}\),然后用 \(\rm CRT\) 合并答案即可。

Code


P4769 [NOI2018] 冒泡排序

首先存在长度为 $3 $ 的下降子序列的序列不是好的。

可以简单理解一下,如果存在,则中间的元素必定要向右一次后向左一次,等于做了无用功,无法卡到下界。

那么我们要求不存在长度为 \(3\) 的下降子序列。

我们不难\(\rm DP\),设 \(f[i][j]\) 表示前 \(i\) 个数 ,最大数为 \(j\) 的方案数。如果\(a_{i+1}>j\),则\(f[i][j]\to f[i+1][a_{i+1}]\)。否则,\(a_{i+1}\)必须为最小的数,有\(f[i][j]\to f[i+1][j]\)。

我们把 \(f[i][j]\) 看成平面直角坐标系的点 \((i,j)\),我们从\((0,0)\)开始,要到\((n,n)\),每次可以直接向右移动一步,也可以向右上移动。

向右上移动看起来非常没有规律,改一下,将向右上移动等价于先向上移动\(k\)步,再向右移动一步。

这就很清晰了,我们从原点开始,到\((n,n)\)结束,每次可以选择向右或向上移动一步的方案数。

还有一个现实条件\(i\le j\),因为排列中 \(i\) 个数的最大值一定不小于 \(i\),在平面上,就是路径穿过 \(y=x\) 的直线。

这就是经典组合模型,直接折线法,\(\rm Ans=\dbinom{2n}{n}-\dbinom{2n}{n-1}\) 。

考虑字典序的限制条件,类似于数位\(\rm DP\),我们前求出前\(i\)个走到的点,并计算当前点到终点的方案数。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 1200006
#define P 998244353
using namespace std;
int Pow(int x,int y){
	int now=1;
	for(;y;y>>=1,x=1LL*x*x%P)if(y&1)now=1LL*now*x%P;
	return now;
}
int fac[N],inv[N],n;
int C(int x,int y){
	if(x<y||y<0||x<0)return 0;
	return 1LL*fac[x]*inv[y]%P*inv[x-y]%P;
}
int get(int x,int y){return (C(n-x+n-y,n-x)-C(n-x+n-y,n-y-1))%P;}
int a[N],c[N];
inline void add(int x){for(;x<=n;x+=x&-x)c[x]++;}
inline int ask(int x){int sum=0;for(;x;x-=x&-x)sum+=c[x];return sum;}
void solve(){
	memset(c,0,sizeof(c));
	scanf("%d",&n);
	rep(i,1,n)scanf("%d",&a[i]);
	int y=0,ans=0;
	rep(i,1,n-1){
		if(a[i]<y){
			ans=(ans+get(i-1,y+1))%P;
			if(ask(a[i])!=a[i]-1)break;
		}
		else ans=(ans+get(i-1,a[i]+1))%P;
		y=max(a[i],y);add(a[i]);
	}
	printf("%d\n",(ans+P)%P);
}
int main(){
	fac[0]=inv[0]=1;
	rep(i,1,N-5)fac[i]=1LL*fac[i-1]*i%P,inv[i]=Pow(fac[i],P-2);
	int T;scanf("%d",&T);
	while(T--)solve();
	return 0;
}

P4931 [MtOI2018]情侣?给我烧了!(加强版)

不难想到枚举匹配的情侣和他们坐的位置,有\(\dbinom{n}{k}^2k!\) 的方案数。

考虑剩下的情侣,错排问题,定义 \(D_i\) 为 \(i\) 对情侣的错排方案,不考虑排列顺序。

经典错排问题中\(D_i=(i-1)(D_{i-1}+D_{i-2})\) 。

这里错排的情侣可以男女配对,也可以同行配对,所以方案数翻倍。

\[D_i=2(i-1)(D_{i-1}+D_{i-2}) \]

还考虑每对座椅分左右,再\(\times 2^n\)

总的方案数为\(\dbinom{n}{k}^2k!2^n(n-k)!D_{n-k}\),直接递推即可。

这里要线性复杂度,所以预处理阶乘,次幂,并线性求逆元。

这样我们不用生成函数解决了这道题。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 5000005
#define P 998244353
using namespace std;
int w = N - 5 , inv[N] , fac[N] , d[N] , pw[N];
int C(int n,int m){
	if(n<0||m<0||n<m)return 0;
	return 1LL*fac[n]*inv[m]%P*inv[n-m]%P;
}
int main(){
	pw[0]=1;
	rep(i,1,w)pw[i]=(pw[i-1]<<1)%P;
	inv[1]=1;rep(i,2,w)inv[i]=-1LL*(P/i)*inv[P%i]%P;
	fac[0]=inv[0]=1;
	rep(i,1,w)fac[i]=1LL*fac[i-1]*i%P,inv[i]=1LL*inv[i-1]*inv[i]%P;
	d[0]=1;d[1]=0;
	rep(i,2,w)d[i]=1LL*2*(i-1)*(d[i-1]+d[i-2])%P;
	int T;scanf("%d",&T);
	while(T--){
		int n,k;scanf("%d%d",&n,&k);
		printf("%lld\n",1LL*C(n,k)*C(n,k)%P*fac[k]%P*pw[n]%P*d[(n-k)]%P*fac[n-k]%P);
	}
	return 0;
}

P3726 [AH2017/HNOI2017]抛硬币

难度很高的一题。

首先考虑 \(a=b\) 的情况。

对于任意一种 \(A\) 赢了 \(B\) 的情况,将每次抛硬币正负取反,对应于一种 \(B\) 赢了 \(A\) 的情况。

那么我们将所有情况分为平局和不平局的情况,\(A\)赢了的情况显然是不平局的情况的\(\dfrac{1}{2}\) 。

所以我们只用计算平局的情况,枚举正面向上的次数,得到\(\sum\limits_{i=0}^a \dbinom{n}{i}^2\),卷积一下得到\(\dbinom{2n}{n}\) 。

考虑 \(a>b\) 的情况。

首先对于平局\(x=y\)的情况,\(a-x>b-y\),取反后一定是 \(A\) 赢 。

对于 \(A\) 输局 \(x<y\) 的情况,\(a-x>b-y\),取反后也一定是 \(A\) 赢。

对于 \(A\) 赢局 \(x>y\) 的情况,无法判断 \(a-x\) 和 \(b-y\) 的大小。

我们发现前两种情况都是输赢对应,最后一种可能输赢对应,也可能都是赢。

我们计算都是赢的情况,有\(a-x>b-y\to a-b>x-y\) ,考虑到\(a-b\le 10^4\),我们可以枚举 \(y\) 和 \(x-y\),令 \(k=x-y\) 。

\[\begin{aligned} sum&=\sum\limits_{k=1}^{a-b}\sum\limits_{y=0}^b\binom{a}{y+k}\binom{b}{y}\\&=\sum\limits_{k=1}^{a-b}\sum\limits_{y=0}^b\binom{a}{y+k}\binom{b}{b-y}\\&=\sum\limits_{k=1}^{a-b}\binom{a+b}{k+b}\end{aligned} \]

然后直接计算组合数即可,模数不为质数,考虑扩展\(\rm Lucas\)。


P1655 小朋友的球\

斯特林数。

递推式

\[\begin{Bmatrix}n\\m\end{Bmatrix}=\begin{Bmatrix}n-1\\m-1\end{Bmatrix}+m\begin{Bmatrix}n-1\\m\end{Bmatrix} \]

组合意义,考虑第 \(n\) 个元素重新分组还是加入原先的组。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define N 105
long long s[N][N];
int main(){
	s[1][1]=1;
	rep(i,2,N-5)rep(j,1,i)s[i][j]=s[i-1][j]*j+s[i-1][j-1];
	int n,m;
	while(~scanf("%d%d",&n,&m))printf("%lld\n",s[n][m]);
	return 0;
}
//这道题需要高精度

P5395 第二类斯特林数·行

可能有用的模板。

考虑将 \(n\) 个球放入 \(m\) 个不同盒子中,允许有空盒子,得到恒等式。

\[m^n=\sum\limits_{i=0}^{m}\binom{m}{i}\begin{Bmatrix}n\\i\end{Bmatrix}i! \]

令\(f(x)=x^n,g(x)=\begin{Bmatrix}n\\x\end{Bmatrix}x!\),发现上面就是个二项式反演的标准形式。一波推导后的到

\[\begin{Bmatrix}n\\i\end{Bmatrix}=\sum\limits_{i=0}^{m}\dfrac{(-1)^{m-i}i^n}{(m-i)!i!} \]

发现这就是一个卷积,直接\(\rm NTT\)即可。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 600005
#define P 167772161
using namespace std;
int Pow(int x,int y){
	if(y<0)return Pow(Pow(x,P-2),-y);
	int now=1;
	for(;y;y>>=1,x=1LL*x*x%P)if(y&1)now=1LL*now*x%P;
	return now;
}
int n,a[N],b[N],rev[N],t,fac[N];
void ntt(int *u,int op){
	rep(i,0,t-1)if(rev[i]>i)swap(u[rev[i]],u[i]);
	for(int l=1,k=2;k<=t;k<<=1,l<<=1){
		int cur=Pow(3,(P-1)/k*op);
		for(int i=0;i<t;i+=k){
			int now=1;
			rep(j,0,l-1){
				int x=u[i+j],y=1LL*u[i+j+l]*now%P;
				u[i+j]=(x+y)%P;u[i+j+l]=(x-y)%P;
				now=1LL*now*cur%P;
			}
		}
	}
}
int main(){
	scanf("%d",&n);
	t=1;while(t<=n*2)t<<=1;
	rep(i,1,t-1)rev[i]=(rev[i>>1]>>1)|((i&1)?(t>>1):0);
	fac[0]=1;rep(i,1,n)fac[i]=1LL*fac[i-1]*i%P;
	rep(i,0,n)a[i]=1LL*Pow(i,n)*Pow(fac[i],P-2)%P,b[i]=Pow(-1,i)*Pow(fac[i],P-2);
	ntt(a,1);ntt(b,1);rep(i,0,t-1)a[i]=1LL*a[i]*b[i]%P;ntt(a,-1);
	int cur=Pow(t,-1);
	rep(i,0,n)printf("%lld ",(1LL*cur*a[i]%P+P)%P);
	return 0;
} 

P6620 [省选联考 2020 A 卷] 组合数问题

看起来多项式乘次幂乘组合数非常不可做。

一个比较讨论的思路是通常幂转下降幂。

众所周知

\[x^n=\sum\limits_{i=0}^{n}\begin{Bmatrix}n\\i\end{Bmatrix}x^{\underline{i}} \]

这里多项式直接转下降幂

\[\begin{aligned}Ans&=\sum\limits_{k=0}^{n}\sum\limits_{i=0}^mb_ik^{\underline{i}}\binom{n}{k}x^k\\&=\sum\limits_{k=0}^{n}\sum\limits_{i=0}^mb_in^{\underline{i}}\binom{n-i}{k-i}x^k\\&=\sum\limits_{i=0}^mb_in^{\underline{i}}x^i(x+1)^{n-i}\end{aligned} \]

这里已经可以\(\rm O(M\log M)\)计算,我们还需要计算下降幂的系数\(b_i\),直接斯特林数即可。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define N 1005
int P,n,m,x,a[N],b[N],s[N][N];
int Pow(int x,int y){
	int now=1;
	for(;y;y>>=1,x=1LL*x*x%P)if(y&1)now=1LL*now*x%P;
	return now;
} 
int main(){
	scanf("%d%d%d%d",&n,&x,&P,&m);
	rep(i,0,m)scanf("%d",&a[i]);
	s[0][0]=1;
	rep(i,1,m)rep(j,1,m)s[i][j]=(s[i-1][j-1]+1LL*s[i-1][j]*j)%P;
	rep(i,0,m)rep(j,i,m)b[i]=(b[i]+1LL*a[j]*s[j][i])%P;
	int ans=0;
	rep(i,0,m){
		int cur=1;
		rep(j,n-i+1,n)cur=1LL*cur*j%P;
		ans=(ans+1LL*b[i]*cur%P*Pow(x,i)%P*Pow(x+1,n-i)%P)%P;
	}
	printf("%d\n",ans);
	return 0;
} 

P2532 [AHOI2012]树屋阶梯

卡特兰数。

我们定义 \(f[i]\) 表示 \(i\) 级阶梯的方案数,我们枚举矩形将阶梯分成两部分,得到两个递归子问题,从而得到方程\(f[i]=\sum\limits_{j=0}^{i-1}f[j]\times f[i-j-1]\)。

这就是卡特兰数的递推式,一般卡特兰数写作\(C_i\)

\[C_n=\sum \limits_{i=0}^{n-1}C_iC_{n-i-1} \]

卡特兰数有许多经典模型:

  • 给定\(1\sim n\)的入栈数列,合法的出栈序列数
  • 凸\(n\)边形的三角形划分
  • \(n\)个节点的二叉树个数
  • \(n\)对括号的合法括号序列数

这些我们都可以直接用递推式序列,比如括号匹配,我们定义\(f_i\)表示\(n\)对括号的方案数。显然第一个一定是左括号,我们枚举与之匹配的右括号位置,这就是卡特兰数的递推式。

我们可以换一个思路\(\rm DP\),定义\(f[i][j]\)表示长度为 \(i\) 的序列,剩余 \(j\) 个左括号的方案数,显然 \(j\ge 0\)。

在平面直角坐标系中,就是从原点出发,每次可以向右上或右下走一格,走到\((2n,0)\)的方案数,不能碰到直线 \(y=-1\) 。

直接用折线法可以得到方案数,即为卡特兰数, \(C_n=\dbinom{2n}{n}-\dbinom{2n}{n-1}\)。

把组合数拆开可以得到 \(C_n=\dfrac{(2n)!}{n!(n+1)!}\) 。

同时可以得到一阶递推公式 \(C_n=\dfrac{4n-2}{n+1}C_{n-1}\) 。

Code


P3200 [HNOI2009]有趣的数列

从小到大考虑每个数的填放位置。

不难发现对于奇数位和偶数位,都是从左往右填,且任何时刻奇数位填的数不少于偶数位填的数。

我们定义\(f[i][j]\)表示奇数位填了 \(i\) 个,偶数位填了 \(j\) 个。显然\(f[i][j]\)可以转移到\(f[i+1][j]\),\(f[i][j+1]\),且不能穿过直线 \(y=x\)。

这就是卡特兰数的折现模型,答案为\(C_n\)。

Code


P3978 [TJOI2015]概率论

首先 \(n\) 个节点的二叉树有 \(C_n\)个。

我们将二叉树的叶子删去一个,得到 \(k\) 个大小为 \(n-1\) 的二叉树。

反过来,我们可以在大小为 \(n-1\) 的二叉树上挂一个节点,得到大小为 \(n\) 的二叉树,每个二叉树有 \(n\) 个可以挂的位置。

所以叶子数之和为 \(nC_{n-1}\),答案为\(\dfrac{nC_{n-1}}{C_n}=\dfrac{n(n+1)}{2(2n-1)}\)

#include<bits/stdc++.h>
int main(){
	int n;scanf("%d",&n);
	return printf("%.10lf",1.00*n*(n+1)/2/(2*n-1)),0;
}

容斥原理。

有若干个物品,每个物品有若干属性(可能没有),现在要求出带有属性的物品个数。

我们加上至少带有一个属性的物品,减去至少带有两个属性的物品,再加上至少带有三个,以此类推。

而最简单的容斥就是求补集,有时候答案集合并不好求,我们直接求答案集合的补集。

P5664 [CSP-S2019] Emiya 家今天的饭

简单容斥,考虑总方案数减去不符合条件的。

不符合条件就是某个食材用了超过\(\left\lfloor\dfrac{k}{2}\right\rfloor\)次,最多只有一个食材超过了。我们枚举是哪一个食材超过了,然后减去即可。

其实如果限制是\(\left\lfloor\dfrac{k}{3}\right\rfloor\),甚至是\(\left\lfloor\dfrac{k}{4}\right\rfloor\)都是可以做的,不妨思考一下。

Code


P1450 [HAOI2008]硬币购物

硬币种类很少,考虑容斥。

先不考虑数量限制,直接多重背包求出方案。

然后枚举哪些硬币超过了限制,容斥即可得到答案。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 100005
using namespace std;
int c[4],d[4],s,n,w=N-5;long long f[N];
int main(){
	rep(i,0,3)scanf("%d",&c[i]);
	f[0]=1;
	rep(i,0,3)rep(j,0,w-c[i])f[j+c[i]]+=f[j];
	scanf("%d",&n);
	rep(i,1,n){
		rep(j,0,3)scanf("%d",&d[j]);
		scanf("%d",&s);long long ans=f[s];
		rep(S,1,15){
			long long cur=0,op=1;
			rep(k,0,3)if((S>>k)&1)op*=-1,cur+=(d[k]+1)*c[k];
			if(cur<=s)ans+=op*f[s-cur];
		}
		printf("%lld\n",ans);
	}
	return 0;
}

P4336 [SHOI2016]黑暗前的幻想乡

求 \(n-1\) 个公司每个公司修恰好一条边的生成树个数。

如果不考虑什么公司修,直接矩阵树定理即可。

既然要考虑,直接容斥即可。把哪些公司没有修作为容斥中物品的属性,那么答案就是总方案数减去有属性的物品数量,经典容斥模型。

Code

标签:limits,组合,int,题解,rep,数学,binom,sum,define
来源: https://www.cnblogs.com/SharpnessV/p/15699169.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有