【校内模拟】简单题(容斥)(斯特林反演)(背包)

有一天整数?!觉得自己太胖了,于是想把自己拆开来看看,结果一拆不得了,在拆的过程中他发现了一些人生的奥秘,并想让你也体验一下。

定义?(?, ?)表示将?拆成?个不同正整数的乘积的方案数,注意一种方案的排列仍然是同一种方案,也就是说2×3×5和5×2×3是同一种方案。
比如?(144,4) = 7,分别列出来就是:

144 = 1×2×4×18 = 1×2×8×9 = 1×2×3×24 = 1×2×6×12 = 1×3×4×12= 1×3×6×8 = 2×3×4×6

现在要你回答?(?!, ?) mod 10^9 + 7的值。

满足? ≤ 10000, ? ≤ 30。


题解:

由于要求互不相同,首先忽略大小限制,最后除掉 k ! k! k!即可。

k k k个数,看成是 k k k个点,两个数相同则连一条边。

我们要求没有连边的方案数。

发现所有方案连出来的图都是由若干完全图组成的。

考虑枚举连通块划分,强行令同一块内部相等,不同块之间没有限制。

对于一个连通块划分方案,将原来 k k k个点对应起来的方案数为 k ! ∏ i i ! c n t [ i ] c n t [ i ] ! \frac{k!}{\prod_{i}i!^{cnt[i]}cnt[i]!} ii!cnt[i]cnt[i]!k!

计算方案数显然直接用背包就行了。

接下来考虑容斥。

考虑一个实际大小为 k k k的连通块。

在我们枚举一个大小为 t t t的连通块的时候会被算 S k , t S_{k,t} Sk,t次,其中 S k , t S_{k,t} Sk,t是第二类斯特林数。

我们需要每一个连通块大小都是 1 1 1的方案数。显然一个连通块的容斥系数就是斯特林反演的系数 ( − 1 ) i − 1 ( i − 1 ) ! (-1)^{i-1}(i-1)! (1)i1(i1)!,其中 i i i是这个连通块的大小。一个图的容斥系数就是所有连通块系数的乘积。

暴力枚举 k k k的有序正整数拆分,然后算容斥系数,原图方案数,背包方案数,就没了。


代码:

#include
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++; 
	}
	
	template<typename T>
	inline T get(){
		char c;
		while(!isdigit(c=gc()));T num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int gi(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;

cs int mod=1e9+7;
inline int add(int a,int b){a+=b-mod;return a+(a>>31&mod);}
inline int dec(int a,int b){a-=b;return a+(a>>31&mod);}
inline int mul(int a,int b){ll r=(ll)a*b;return r>=mod?r%mod:r;}
inline int power(int a,int b,int res=1){
	for(;b;b>>=1,a=mul(a,a))(b&1)&&(res=mul(res,a));
	return res;
}
inline void Inc(int &a,int b){a+=b-mod;a+=a>>31&mod;}
inline void Dec(int &a,int b){a-=b;a+=a>>31&mod;}
inline void Mul(int &a,int b){a=mul(a,b);}

cs int N=1e4+7;

int p[N],pc,tim[N],ct[N],px;
bool mark[N];

inline void linear_sieves(int n){
	for(int re i=2;i<=n;++i){
		if(!mark[i])p[++pc]=i;
		for(int re j=1;i*p[j]<=n;++j){
			mark[i*p[j]]=true;
			if(i%p[j]==0)break;
		}
	}
	for(int re i=1;i<=pc;++i){
		for(int re j=p[i];j<=n;j*=p[i])tim[i]+=n/j;
	}
	for(int re i=1;i<=pc;++i){
		px=std::max(tim[i],px);
		++ct[tim[i]];
	}
}

int n,k,ans;
int g[N],fac[N],ifac[N],inv[N];
int cnt[N];

int f[10000];

inline int calc(int mx){
	int res=1;
	int up=px;
	memset(f+1,0,sizeof(int)*up);
	f[0]=1;
	for(int re j=1;j<=mx;++j){
		for(int re k=cnt[j];k;--k)
		for(int re t=j;t<=up;++t)
		Inc(f[t],f[t-j]);
	}
	for(int re i=px;i;--i)if(ct[i]){
		Mul(res,power(f[i],ct[i]));
		if(!res)return 0;
	}
	return res;
}

void dfs(int last,int rest,int coef){
	if(rest==0){Inc(ans,mul(coef,calc(last)));return ;}
	if(rest<last)return ;
	for(int re i=last;i<=rest;++i){
		++cnt[i];
		int v=mul(coef,ifac[i]);
		Mul(v,mul(inv[cnt[i]],g[i]));
		dfs(i,rest-i,v);
		--cnt[i];
	}
}

signed main(){
#ifdef zxyoi
	freopen("jdt.in","r",stdin);
#else
#ifndef ONLINE_JUDGE
	freopen("jdt.in","r",stdin);freopen("jdt.out","w",stdout);
#endif
#endif
	scanf("%d%d",&n,&k);linear_sieves(n);
	g[1]=1;for(int re i=2;i<=100;++i)g[i]=dec(0,mul(i-1,g[i-1]));
	fac[0]=fac[1]=ifac[0]=1;for(int re i=2;i<=100;++i)fac[i]=mul(fac[i-1],i);
	ifac[100]=power(fac[100],mod-2);for(int re i=99;i;--i)ifac[i]=mul(ifac[i+1],i+1);
	inv[0]=inv[1]=1;for(int re i=2;i<=100;++i)inv[i]=mul(mod-mod/i,inv[mod%i]);
	dfs(1,k,1);
	cout<<ans<<"\n";
	return 0;
}

你可能感兴趣的:(组合数学,背包问题,校内模拟)