hdu 4045 Machine scheduling 组合数学

题意是 有n台机器,从中选出r台机器,满足两台的编号差不小于k , 并将这r台,分成m组, 问有多少种组合方式?

第二部分 就是裸的 第二类斯特林数。

重点是第一部分 :

     问题 抽象出来就是  : 从 数字 1 - n 种,选出 r 个 ,相邻的两个差不小于k 。

     这个题的解法有两种。 一种是 dp   一个一个递推。

     第二种就是  组合公式。 要比第一种方法快的多

       解法;  我们选出的 r 个 分别 记为 a1 a2 a3 ...... ar

       我们 令  bn 数列, b1 = a1 , b2 = a2 - (k -1)  , b3 = a3 - 2( k -1) ..... br = ar - (r-1)*( k -1)

      这样 这r 个 ai  和 bi 是一一对应的。 从ai 可以对应到bi  ,从bi 可以对应到 ai 。

     而 bi 数列中 两项的差大于等于1 。bi数列的组合数 就是 C( n - (r -1 )* ( k -1 ) , r )

    求组合数, mod 之后的值, 直接利用逆元 即可求出。

组合数求法

#include <iostream>
#include <cstring>
#include <cstdio>
#define LL long long
using namespace std;
const LL mod = 1000000007;
LL s[1001][1001];
void init(){
    memset(s,0,sizeof(s));
    s[1][1] =1;
    for(int i=2;i<= 1000;i++){
        for(int j = 1;j<=i;j++){
            s[i][j] = s[i-1][j-1] + j* s[i-1][j];
            s[i][j]%= mod;
        }
    }
}
LL pow(LL a,LL b){
    LL ret = 1;
    while(b){
        if(b&1){
            ret *= a;
            ret %= mod;
        }
        a = a*a;
        a %= mod;
        b =b>>1;
    }
    return ret;
}
int n , m , r , k ;
LL get(){
   int a = n - (r-1)*k + r -1;
   if(r>a)return 0;
   LL up = 1;
   for(int i= a- r +1;i<=a;i++){
       up *= i;
       up%= mod;
   }
   LL down  =1;
   for(int i=1;i<=r;i++){
       down *= i;
       down %= mod;
   }
   down = pow(down ,mod - 2);
    up = up*down;
    up%= mod;
    return up;
}
void solve(){
  
    LL ans = get();
    ans %= mod;
    LL p= 0 ;
    for(int i=1;i<= m;i++){
        p+= s[r][i];
        p%= mod;
    }
    ans *= p;
    ans %= mod;
    cout << ans <<endl;
}
int main() {
    init();
    while(~scanf("%d%d%d%d",&n,&r,&k,&m)){
        solve();
    }
    return 0;
}


递推求法

#include <iostream>
#include <cstring>
#include <cstdio>
#define LL long long
using namespace std;
const LL mod = 1000000007;
LL s[1001][1001];
void init(){
    memset(s,0,sizeof(s));
    s[1][1] =1;
    for(int i=2;i<= 1000;i++){
        for(int j = 1;j<=i;j++){
            s[i][j] = s[i-1][j-1] + j* s[i-1][j];
            s[i][j]%= mod;
        }
    }
}
int n , m , r , k ;
LL dp[1001][1001];
LL t[1001];
void init_dp(){
memset(dp,0,sizeof(dp));
    for(int i  =1;i<=n;i++){
        dp[1][i] = 1;
    }
    for(int i =2;i<=r;i++){
        t[0] = 0;
        for(int j = 1;j<=n;j++){
            t[j] = t[j-1]+dp[i-1][j];
        }
        for(int j = k;j<=n;j++){
            if(j>=k){
                dp[i][j] = t[j-k];
            }
            dp[i][j] %= mod;
        }
    }
}
void solve(){
    init_dp();  
    LL ans = 0 ;
    for(int i =r;i<=n;i++){
        ans += dp[r][i];
    }
    ans %= mod;
    LL p= 0 ;
    for(int i=1;i<= m;i++){
        p+= s[r][i];
        p%= mod;
    }
    ans *= p;
    ans %= mod;
    cout << ans <<endl;
}
int main() {
    init();
    while(~scanf("%d%d%d%d",&n,&r,&k,&m)){
        solve();
    }
    return 0;
}







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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值