传送门
(这题没明确讲多组数据害我WA了一发)

可以把题目分成两个部分
- 从n个机器中选出r个
- 将r个机器分成不超过m组

第二个子问题很明显是第二类斯特林数,即 <munderover> i = 1 m </munderover> S r , i

比较棘手的是第一个子问题,网上的题解多是插板法不再赘述,这里提供一种DP的做法。
f [ i ] [ j ] 表示当前选了 j 个数,且所选最大数不大于 i 的方案数
由于相邻元素的差不小于 k ,显然最大数为 i 的方案数是 f [ i k ] [ j 1 ]
再加上最大数不大于 i 1 的方案数,所以可得 f [ i ] [ j ] = f [ i k ] [ j 1 ] + f [ i 1 ] [ j ]
这个子问题的答案就是 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;
}