大意:
求
其中f(k)=
n<=1e9,m<=1000
思路:
大概能想到最后推出来的式子跟n是没啥关系的,看数据范围差不多是m^2的复杂度(不然还做p啊)
然后还有一个东西极大阻碍了我们的推柿子进程,就是f(k),它跟外面的组合数并不能进行什么神奇操作,所以我们可以考虑将其转化一下形式。
这里用到了关于下降幂的知识
下降幂,没错它其实就是组合数,只是换了一个名字,但正是因为它与幂函数有许多可以类比的地方,所以也叫它下降幂。
我们考虑它的一个性质:
证明:
左式
右式
得证
可以稍微想一想,不难发现,有了上述的性质,我们可以把一个跟k有关的下降幂变成与k无关的一个下降幂,就多了很多操作的空间。
所以考虑将f(k),一个普通多项式,转化为一个下降幂多项式
也就是令,这里bi未知,但实际上我们后面可以在m^2的复杂度内暴力求解,这里先不提
原式转化
注意到后面那一坨已经跟k没关系了,考虑一下前面那一坨
跟二项式定理有一点像,并且我们又注意到只有k>=i时才有意义,不然都是0了,
所以我们考虑枚举k-i,
不难发现,前面已经是一个二项式定理的样子了,这也就意味着我们成功把n的复杂度降下来了!
如果预处理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个集合当中
它跟下降幂的一个关系就是:
考虑这样一个组合意义:有k种颜色,每一个人选择任意一种的方案数
等式左边显然就代表这个方案数,我们也可以考虑考虑枚举颜色
假设当前用总共用了k种颜色给x个人,方案数就是将n个人分成k个集合,并枚举k种颜色
回顾一下f(k)的定义
考虑改变枚举顺序,因为最后要变成枚举k的幂次,也就是j的形式:
到这里就整理出b数组了
第二类斯特林数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;
}
完结撒花~