hdu 4045 Machine scheduling 组合数学

本文探讨了从n台机器中选择r台,确保任意两台编号差不小于k的问题,并将其分为m组的方法数。提供了两种解法:一是动态规划递推;二是使用组合数学中的公式快速计算。

题意是 有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;
}







### HDU OJ 排列组合问题解法 排列组合问题是算法竞赛中的常见题型之一,涉及数学基础以及高效的实现技巧。以下是关于如何解决此类问题的一些通用方法和具体实例。 #### 数学基础知识 在处理排列组合问题时,需要熟悉以下几个基本概念: - **阶乘计算**:用于求解全排列的数量 $ n! = n \times (n-1) \times ... \times 1 $[^4]。 - **组合数公式**:$ C(n, k) = \frac{n!}{k!(n-k)!} $ 表示从 $ n $ 中选取 $ k $ 的方案数[^5]。 - **快速幂运算**:当涉及到模运算时,可以利用费马小定理优化逆元的计算[^6]。 #### 题目推荐与分析 以下是一些典型的 HDU OJ 上的排列组合题目及其可能的解法: ##### 1. 基础排列组合计数 - **HDU 2039 近似数** - 描述:给定两个整数 $ a $ 和 $ b $,统计区间内的近似数数量。 - 方法:通过枚举每一位上的可能性来构建合法数字并计数[^7]。 ```cpp #include <iostream> using namespace std; long long comb(int n, int r){ if(r > n || r < 0)return 0; long long res=1; for(int i=1;i<=r;i++)res=res*(n-i+1)/i; return res; } int main(){ int t,n,k; cin>>t; while(t--){ cin>>n>>k; cout<<comb(n+k-1,k)<<endl; // 组合数应用 } } ``` ##### 2. 动态规划的应用 - **HDU 1028 Ignatius and the Princess III** - 描述:给出正整数 $ m $ 和 $ n $,问有多少种方式把 $ m $ 分成最多 $ n $ 份。 - 方法:定义状态转移方程 $ dp[i][j]=dp[i-1][j]+dp[i][j-i] $ 来表示当前总和为 $ j $ 并分成至多 $ i $ 份的情况数目[^8]。 ```cpp #include<bits/stdc++.h> using namespace std; const int MAXN=1e3+5; long long c[MAXN][MAXN]; void init(){ memset(c,0,sizeof(c)); c[0][0]=1; for(int i=1;i<MAXN;i++){ c[i][0]=c[i][i]=1; for(int j=1;j<i;j++) c[i][j]=(c[i-1][j-1]+c[i-1][j])%(1e9+7); } } int main(){ init(); int T,m,n; scanf("%d",&T); while(T--){ scanf("%d%d",&m,&n); printf("%lld\n",c[m+n-1][min(m,n)]); } } ``` #### 总结 针对不同类型的排列组合问题,可以选择合适的工具和技术加以应对。无论是简单的直接计算还是复杂的动态规划模型,都需要扎实的基础知识作为支撑。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值