下降幂+第二类斯特林数 推柿子大题

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

大意:

\sum_{k=0}^{n} f(k)*x^k*\binom{n}{k}

其中f(k)=\sum_{i=0}^{m} a_i*k^i

n<=1e9,m<=1000

思路:

大概能想到最后推出来的式子跟n是没啥关系的,看数据范围差不多是m^2的复杂度(不然还做p啊)

然后还有一个东西极大阻碍了我们的推柿子进程,就是f(k),它跟外面的组合数并不能进行什么神奇操作,所以我们可以考虑将其转化一下形式。

这里用到了关于下降幂的知识


下降幂n^{\underline{k}}=n(n-1)(n-2)...(n-k+1)=\frac{n!}{(n-k)!},没错它其实就是组合数,只是换了一个名字,但正是因为它与幂函数有许多可以类比的地方,所以也叫它下降幂。

我们考虑它的一个性质:

\binom{n}{k}k^{\underline{m}}=\binom{n-m}{k-m}n^{\underline{m}}

证明:

左式=\frac{n!}{k!(n-k)!}\frac{k!}{(k-m)!}=\frac{n!}{(n-k)!(k-m)!}

右式=\frac{(n-m)!}{(n-k)!(k-m)!}\frac{n!}{(n-m)!}=\frac{n!}{(n-k)!(k-m)!}

得证


可以稍微想一想,不难发现,有了上述的性质,我们可以把一个跟k有关的下降幂变成与k无关的一个下降幂,就多了很多操作的空间。

所以考虑将f(k),一个普通多项式,转化为一个下降幂多项式

也就是令\sum_{i=0}^{m} a_i*k^i=\sum_{i=0}^{m} b_i*k^{\underline{i}},这里bi未知,但实际上我们后面可以在m^2的复杂度内暴力求解,这里先不提

原式转化

\sum_{k=0}^{n} f(k)*x^k*\binom{n}{k}=\sum_{k=0}^{n} (\sum_{i=0}^{m}b_ik^{\underline{i}})x^k\binom{n}{k}=\sum_{k=0}^{n} (\sum_{i=0}^{m}b_ik^{\underline{i}}\binom{n}{k})x^k =\sum_{k=0}^{n} (\sum_{i=0}^{m}b_i\binom{n-i}{k-i}n^{\underline{i}})x^k

=\sum_{k=0}^{n} \binom{n-i}{k-i}x^k(\sum_{i=0}^{m}b_in^{\underline{i}})

注意到后面那一坨已经跟k没关系了,考虑一下前面那一坨

跟二项式定理有一点像,并且我们又注意到只有k>=i时才有意义,不然都是0了,

所以我们考虑枚举k-i,

=\sum_{k=0}^{n-i} \binom{n-i}{k}x^{k+i}(\sum_{i=0}^{m}b_in^{\underline{i}})

=\sum_{k=0}^{n-i} \binom{n-i}{k}x^{k}(\sum_{i=0}^{m}b_in^{\underline{i}})x^i

不难发现,前面已经是一个二项式定理的样子了,这也就意味着我们成功把n的复杂度降下来了!

=\sum_{i=0}^{m}b_in^{\underline{i}}x^i(x+1)^{n-i}

如果预处理b数组的话,这个式子的复杂度就是O(m)

接下来看看如何处理b数组

这里要用到第二类斯特林数(远古的记忆...)


第二类斯特林数S(n,k)表示将n个小球分成k个非空集合的方案数,也就是小球不同,但是集合相同

n<k时S(n,k)=0

所以不难得到它的递推式:

S(n,k)=S(n-1,k-1)+k*S(n-1,k)

对于第n个小球,它可以自己作为一个集合,也可以插入到之前的k个集合当中

它跟下降幂的一个关系就是:
n^x=\sum_{k=0}^{n}S(n,k)x^{\underline{k}}

考虑这样一个组合意义:有k种颜色,每一个人选择任意一种的方案数

等式左边显然就代表这个方案数,我们也可以考虑考虑枚举颜色

假设当前用总共用了k种颜色给x个人,方案数就是将n个人分成k个集合,并枚举k种颜色


回顾一下f(k)的定义

\sum_{i=0}^{m} a_i*k^i=\sum_{i=0}^{m} a_i*\sum_{j=0}^{i}S(i,j)k^{\underline{j}}

考虑改变枚举顺序,因为最后要变成枚举k的幂次,也就是j的形式:

=\sum_{j=0}^{m} k^{\underline{j}}\sum_{i=j}^{m}S(i,j)a_i

=\sum_{i=0}^{m} k^{\underline{i}}\sum_{j=i}^{m}S(j,i)a_j

到这里就整理出b数组了

b_i=\sum_{j=i}^{m} a_j*S(j,i)

第二类斯特林数O(m^2)暴力预处理,bi即可线性推出

到此为止这道题就算结束了,再来回顾一遍

通过下降幂将式子换成可以操作的形式,这一步没见过一般是想不到的,但是今天见到了,以后就得长个心眼了。然后中间就是老老实实推柿子,最后用第二类斯特林数来推出b数组,也算是复习了一下斯特林数

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll N=1010;
ll n,x,mod,m;
ll ksm(ll x,ll y)
{
	ll ans=1;
	while(y)
	{
		if(y&1) ans=ans*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ans;
}
ll inv(ll x)
{
	return ksm(x,mod-2);
}
ll p[N];
ll pp[N];
ll a[N],b[N],S[N][N],np[N],xp[N];
void init()
{
	p[0]=1;
	for(int i=1;i<=1000;++i) p[i]=p[i-1]*i%mod;
	pp[1000]=inv(p[1000]);
	for(int i=1000-1;i>=0;--i)
	{
		pp[i]=pp[i+1]*(i+1)%mod;
	}	
}
void init2()
{
	S[0][0]=1;
	for(int i=1;i<=m;++i)
	{
		for(int j=1;j<=i;++j)
		{
			S[i][j]=(S[i-1][j-1]+j*S[i-1][j]%mod)%mod;
		}
	}
}
void init3()
{
	for(int i=0;i<=m;++i)
	{
		for(int j=i;j<=m;++j)
		{
			b[i]=(b[i]+a[j]*S[j][i]%mod)%mod;
		}
	}
}
void init4()
{
	np[0]=1;
	for(int i=1;i<=m;++i) np[i]=np[i-1]*(n-i+1)%mod;
	
	xp[0]=1;
	for(int i=1;i<=m;++i) xp[i]=xp[i-1]*x%mod;
	
//	xxp[0]=1;
//	for(int i=1;i<=m;++i) xxp[i]=xxp[i-1]*(x+1)%mod;
}
void solve()
{
	cin>>n>>x>>mod>>m;
	for(int i=0;i<=m;++i) cin>>a[i];
	init();
	init2();
	init3();
	init4();
//	for(int i=0;i<=m;++i) cout<<xxp[i]<<" ";
//	cout<<endl;
	ll ans=0;
	for(int i=0;i<=m;++i)
	{
		//cout<<b[i]<<" "<<np[i]<<' '<<xp[i]<<' '<<xxp[n-i]<<endl;
		ans=(ans+b[i]*np[i]%mod*xp[i]%mod*ksm(x+1,n-i)%mod)%mod;
	}
	cout<<ans<<endl;
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	solve();
	return 0;
}

update

经过笔者又一段时间的学习,发现这其实就是一个第二类斯特林数展开幂次函数的套路,配合一些恒等式往往有奇效

这里有一个双倍经验

用到的方法基本跟本题差不多,理论上是可以自己轻松推出来的(如果我有讲明白),时间复杂度是k^2,但是题解区有个佬能做到O(k),以后可能会补

代码贴一下

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll N=5010;
const ll mod=1e9+7;
ll n,m;
ll ksm(ll x,ll y)
{
	ll ans=1;
	while(y)
	{
		if(y&1) ans=ans*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ans;
}
ll np[N];
ll S[N][N];
void init2()
{
	S[0][0]=1;
	for(ll i=1;i<=m;++i)
	{
		for(ll j=1;j<=i;++j)
		{
			S[i][j]=(S[i-1][j-1]+j*S[i-1][j]%mod)%mod;
		}
	}
}

void init4()
{
	np[0]=1;
	for(ll i=1;i<=m;++i) np[i]=np[i-1]*(n-i+1)%mod;
}
void solve()
{
	cin>>n>>m;

	init2();
	init4();
	ll ans=0;
	for(int i=0;i<=m;++i)
	{
		if(i>n) break;
		ans=(ans+np[i]*S[m][i]%mod*ksm(2,n-i)%mod)%mod;
	}
	cout<<ans<<endl;
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	solve();
	return 0;
}

完结撒花~

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值