传送门
(这题没明确讲多组数据害我WA了一发)
可以把题目分成两个部分
- 从n个机器中选出r个
- 将r个机器分成不超过m组
第二个子问题很明显是第二类斯特林数,即 ∑mi=1Sr,i ∑ i = 1 m S r , i
比较棘手的是第一个子问题,网上的题解多是插板法不再赘述,这里提供一种DP的做法。
用
f[i][j]
f
[
i
]
[
j
]
表示当前选了
j
j
个数,且所选最大数不大于的方案数
由于相邻元素的差不小于
k
k
,显然最大数为的方案数是
f[i−k][j−1]
f
[
i
−
k
]
[
j
−
1
]
再加上最大数不大于
i−1
i
−
1
的方案数,所以可得
f[i][j]=f[i−k][j−1]+f[i−1][j]
f
[
i
]
[
j
]
=
f
[
i
−
k
]
[
j
−
1
]
+
f
[
i
−
1
]
[
j
]
这个子问题的答案就是
f[n][r]
f
[
n
]
[
r
]
两个子问题答案相乘就是最终答案了。实现上还有些细节可以看代码。
这种做法的缺点在于没法预处理,对每组数据都要重新DP,数据组数多的话肯定会T的。
大家还是出门左转用组合数吧(组合数大法好)
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define LL long long
const int N=1001;
const int p=1e9+7;
LL n,r,k,m,s[N][N],f[N][N],ans;
void read(LL &x){
char ch=getchar();x=0;
for(;ch<'0'||ch>'9';ch=getchar());
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
}
void add(LL &x,LL y){
x+=y;
while(x>=p) x-=p;
}
int main(){
s[0][0]=1;
for(int i=1;i<N;i++)
for(int j=1;j<=i;j++)
s[i][j]=(s[i-1][j]*j%p+s[i-1][j-1])%p; //预处理斯特林数
while(cin>>n>>r>>k>>m){
//memset(f,0,sizeof f);
for(int i=1;i<=n;i++){ //dp一波
f[i][0]=1;f[i][1]=i;
for(int j=2;j<=r;j++){
if (i-k>0) f[i][j]=f[i-k][j-1];
else f[i][j]=0;
add(f[i][j],f[i-1][j]);
}
}
ans=0;
for(int i=1;i<=m;i++) add(ans,s[r][i]);
//cout<<ans<<endl;
ans=ans*f[n][r]%p;
//cout<<f[n][r]<<endl;
cout<<ans<<endl;
}
return 0;
}