洛谷 P5824 十二重计数法 题解

题目传送门

题目大意: 求出 【球相同/球不同】【盒子相同/盒子不同】【无限制/每个盒子至少放一个/每个盒子至多放一个】 12 12 12 种搭配下,有多少种不同的方法放球?

题解

Part 1

m n m^n mn,即每个球随便放。

if(type==1){
	return ksm(m,n);
}
Part 2

每个盒子只有放和不放两种状态,即 n ! C m n n!C_m^n n!Cmn,表示选出 n n n 个放的,并且球顺序随意。

if(type==2){
	return 1ll*C(m,n)*fac[n]%mod;
}
Part 3

考虑简单的容斥,设 i i i 表示至少有 i i i 个空盒子:
∑ i = 0 m ( − 1 ) i C m i ( m − i ) n \sum_{i=0}^m (-1)^i C_m^i (m-i)^n i=0m(1)iCmi(mi)n

if(type==3){
	int re=0;
	for(int i=0;i<=m;i++){
		if(i&1)dec(re,1ll*C(m,i)*ksm(m-i,n)%mod);
		else add(re,1ll*C(m,i)*ksm(m-i,n)%mod);
	}
	return re;
}
Part 4

其实就是第二类斯特林数前 m m m 项之和,预处理出第 n n n 行第二类斯特林数即可。

也可以直接上多项式 exp ⁡ \exp exp,就是这题

if(type==4){
	for(int i=0;i<=n;i++){
		if(i&1)F[i]=mod-inv_fac[i];else F[i]=inv_fac[i];
		G[i]=1ll*ksm(i,n)*inv_fac[i]%mod;
	}
	NTT(F,G,n+1);int re=0;
	for(int i=0;i<=m;i++)add(re,i<=n?F[i]:0);
	return re;
}
Part 5

容易发现方案唯一……

if(type==5){
	return n<=m;
}
Part 6

这是第二类斯特林数的定义,直接用Part 4求出来的解就可以了。

if(type==6){
		return F[m];
	}
Part 7

对每个盒子构造 O G F OGF OGF,就是 1 + x 1 + x 2 + . . . = 1 1 − x 1+x^1+x^2+...=\dfrac 1 {1-x} 1+x1+x2+...=1x1,答案为 1 ( 1 − x ) m \dfrac 1 {(1-x)^m} (1x)m1 的第 n n n 项系数。

开玩笑的……虽然那样也能求,但实际上这是个裸的插板法。

if(type==7){
	return C(n+m-1,m-1);
}
Part 8

容易发现就是从 m m m 个盒子里选出 n n n 个有球的。

if(type==8){
	return C(m,n);
}
Part 9

这就是两个板之间不能为空的插板法,即 C n − 1 m − 1 C_{n-1}^{m-1} Cn1m1

if(type==9){
	return C(n-1,m-1);
}
Part 10

这个东西有一个经典的 dp \text{dp} dp:设 f i , j f_{i,j} fi,j 表示 i i i 个球放 j j j 个盒的方案数,为了不计重,假设盒子里的球数是不下降的,那么有: f i , j = f i − j , j + f i , j − 1 f_{i,j}=f_{i-j,j}+f_{i,j-1} fi,j=fij,j+fi,j1 f i − j , j f_{i-j,j} fij,j 是往每个盒里放一个球, f i , j − 1 f_{i,j-1} fi,j1 是新增一个空盒放在最后。

考虑构造生成函数,令 F j ( x ) = ∑ i = 0 f i , j x i F_j(x)=\sum_{i=0} f_{i,j}x^i Fj(x)=i=0fi,jxi,从 F j − 1 F_{j-1} Fj1 转移到 F j F_j Fj 时,先新增了一个盒子,然后往每个盒子里同时放了若干个球,于是有: F j = F j − 1 ( 1 + x j + x 2 j + . . . ) = F j − 1 × 1 1 − x j F_j=F_{j-1}(1+x^j+x^{2j}+...)=F_{j-1}\times\dfrac 1 {1-x^j} Fj=Fj1(1+xj+x2j+...)=Fj1×1xj1

进一步得到: F m = ∏ i = 1 m 1 1 − x i F_m=\prod_{i=1}^m \dfrac 1 {1-x^i} Fm=i=1m1xi1,类似付公主的背包,考虑将这些项求 ln ⁡ \ln ln 加起来再求 exp ⁡ \exp exp,对 1 1 − x i \dfrac 1 {1-x^i} 1xi1 ln ⁡ \ln ln 得到 ∑ j = 1 x i j j \sum_{j=1}\dfrac {x^{ij}} j j=1jxij

最后 [ x n ] F m [x^n]F_m [xn]Fm 就是答案,代码如下:

if(type==10){
	MS(F,19);MS(G,19);
	for(int i=1;i<=m;i++){
		for(int j=1;i*j<=n;j++){
			add(F[i*j],inv[j]);
		}
	}
	getexp(F,G,n+1);return G[n];
}
Part 11

Part 5是一样的……

if(type==11){
	return n<=m;
}
Part 12

先往每个盒子里放一个球就变成Part 10了。

if(type==12){
	return n>=m?G[n-m]:0;
}
完整代码
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define maxn 600010
#define mod 998244353
#define bin(x) (1<<(x))
#define MS(f,lg) memset(f,0,4<<(lg))
#define CP(f,g,ln) memcpy(f,g,(ln)<<2)

int n,m;
int fac[maxn],inv[maxn],inv_fac[maxn];
void FacInit(){
	fac[0]=inv_fac[0]=1;
	for(int i=1;i<=n+m;i++){
		fac[i]=1ll*fac[i-1]*i%mod;
		inv_fac[i]=1ll*inv_fac[i-1]*inv[i]%mod;
	}
}
int C(int x,int y){
	if(x<y||y<0)return 0;
	return 1ll*fac[x]*inv_fac[y]%mod*inv_fac[x-y]%mod;
}
void add(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);}
void dec(int &x,int y){x=(x-y<0?x-y+mod:x-y);}
int add(int x){return x>=mod?x-mod:x;}
int dec(int x){return x<0?x+mod:x;}
int ksm(int x,int y){int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;}
int w[maxn];void prep(int lg){int N=bin(lg);
	inv[1]=1;for(int i=2;i<=maxn-10;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(int i=1,wn;i<N;i<<=1){
		w[i]=1;wn=ksm(3,(mod-1)/(i<<1));
		for(int j=1;j<i;j++)w[i+j]=1ll*w[i+j-1]*wn%mod;
	}
}
int limit,r[maxn];
void InitR(int lg){for(int i=1;i<bin(lg);i++)r[i]=(r[i>>1]>>1)|((i&1)<<(lg-1));}
void ntt(int *f,int lg,int type=0){
	limit=bin(lg);if(type)reverse(f+1,f+limit);
	for(int i=1;i<limit;i++)if(i<r[i])swap(f[i],f[r[i]]);
	for(int mid=1,t;mid<limit;mid<<=1)for(int j=0;j<limit;j+=(mid<<1))for(int i=0;i<mid;i++)
	{t=1ll*f[j+i+mid]*w[mid+i]%mod;f[j+i+mid]=dec(f[j+i]-t);f[j+i]=add(f[j+i]+t);}
	if(type)for(int i=0;i<limit;i++)f[i]=1ll*f[i]*inv[limit]%mod;
}
int F[maxn],G[maxn];
void NTT(int *f,int *g,int ln){
	int lg=ceil(log2(ln*2-1));InitR(lg);ntt(f,lg);ntt(g,lg);
	for(int i=0;i<bin(lg);i++)f[i]=1ll*f[i]*g[i]%mod;ntt(f,lg,1);
}
void getinv(int *f,int *g,int ln){
	static int A[maxn],B[maxn];
	if(ln==1){g[0]=ksm(f[0],mod-2);return;}getinv(f,g,(ln+1)>>1);
	int lg=ceil(log2(ln*2-1));MS(A,lg);MS(B,lg);CP(A,f,ln);CP(B,g,ln);
	InitR(lg);ntt(A,lg);ntt(B,lg);for(int i=0;i<bin(lg);i++)B[i]=1ll*B[i]*(2-1ll*A[i]*B[i]%mod+mod)%mod;
	ntt(B,lg,1);for(int i=0;i<ln;i++)g[i]=B[i];
}
void Dao(int *f,int *g,int ln){for(int i=0;i<ln-1;i++)g[i]=1ll*f[i+1]*(i+1)%mod;g[ln-1]=0;}
void Jifen(int *f,int *g,int ln){for(int i=ln-1;i>0;i--)g[i]=1ll*f[i-1]*inv[i]%mod;g[0]=0;}
void getln(int *f,int *g,int ln){
	static int C[maxn],D[maxn];
	int lg=ceil(log2(ln*2+1));MS(C,lg);MS(D,lg);
	Dao(f,C,ln);getinv(f,D,ln);NTT(C,D,ln);Jifen(C,g,ln);
}
void getexp(int *f,int *g,int ln){
	static int E[maxn];
	if(ln==1){g[0]=1;return;}getexp(f,g,(ln+1)>>1);
	int lg=ceil(log2(ln*2-1));MS(E,lg);getln(g,E,ln);
	for(int i=0;i<ln;i++)E[i]=dec(f[i]-E[i]);E[0]++;NTT(g,E,ln);
}
int solve(int type){
	if(type==1){
		return ksm(m,n);
	}
	if(type==2){
		return 1ll*C(m,n)*fac[n]%mod;
	}
	if(type==3){
		int re=0;
		for(int i=0;i<=m;i++){
			if(i&1)dec(re,1ll*C(m,i)*ksm(m-i,n)%mod);
			else add(re,1ll*C(m,i)*ksm(m-i,n)%mod);
		}
		return re;
	}
	if(type==4){
		for(int i=0;i<=n;i++){
			if(i&1)F[i]=mod-inv_fac[i];else F[i]=inv_fac[i];
			G[i]=1ll*ksm(i,n)*inv_fac[i]%mod;
		}
		NTT(F,G,n+1);int re=0;
		for(int i=0;i<=m;i++)add(re,i<=n?F[i]:0);
		return re;
	}
	if(type==5){
		return n<=m;
	}
	if(type==6){
		return m<=n?F[m]:0;
	}
	if(type==7){
		return C(n+m-1,m-1);
	}
	if(type==8){
		return C(m,n);
	}
	if(type==9){
		return C(n-1,m-1);
	}
	if(type==10){
		MS(F,19);MS(G,19);
		for(int i=1;i<=m;i++){
			for(int j=1;i*j<=n;j++){
				add(F[i*j],inv[j]);
			}
		}
		getexp(F,G,n+1);return G[n];
	}
	if(type==11){
		return n<=m;
	}
	if(type==12){
		return n>=m?G[n-m]:0;
	}
}

int main()
{
	scanf("%d %d",&n,&m);prep(ceil(log2((n+1)<<1)));FacInit();
	for(int i=1;i<=12;i++)printf("%d\n",solve(i));
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值