倪文迪陪你学蓝桥杯2021寒假每日一题:2.1日(2019省赛A组第10题)

2021年寒假每日一题,2017~2019年的省赛真题。本文内容由倪文迪(华东理工大学计算机系软件192班)和罗勇军老师提供。每日一题,关注蓝桥杯专栏: https://blog.csdn.net/weixin_43914593/category_10721247.html

每题提供C++、Java、Python三种语言的代码。

2019省赛A组第10题“组合数问题” ,题目链接:
http://oj.ecustacm.cn/problem.php?id=1461
https://www.dotcpp.com/oj/problem2303.html

1、题目描述


n , m , k n,m,k n,m,k,求有多少对 ( i , j ) (i,j) (i,j)满足 1 ≤ i ≤ n , 0 ≤ j ≤ m i n ( i , m ) 1 ≤ i ≤ n,0 ≤ j ≤ min(i,m) 1in,0jmin(i,m) C i j ≡ 0 ( m o d   k ) C_i^j ≡ 0(mod \ k) Cij0(mod k) k k k 是质数。其中 C i j C_i^j Cij是组合数,表示从 i i i个不同的数中选出 j j j 个组成 一个集合的方案数。

输入:第一行两个数 t, k,其中 t 代表该测试点包含 t 组询问,k 的意思与上文中 相同。
接下来 t 行每行两个整数 n, m,表示一组询问。
输出:输出 t 行,每行一个整数表示对应的答案。由于答案可能很大,请输出答 案除以 1 0 9 + 7 10^9 + 7 109+7 的余数。
数据规模
40%: 1 ≤ k ≤ 100 , 1 ≤ t ≤ 1 0 5 , 1 ≤ n , m ≤ 2000 1 ≤ k ≤ 100, 1 ≤ t ≤ 10^5,1 ≤ n,m ≤ 2000 1k100,1t105,1n,m2000
100%: 1 ≤ k ≤ 1 0 8 , 1 ≤ t ≤ 1 0 5 , 1 ≤ n , m ≤ 1 0 18 1 ≤ k ≤ 10^8, 1 ≤ t ≤ 10^5,1 ≤ n,m ≤ 10^{18} 1k108,1t105,1n,m1018


2、组合数的计算

  式子 C i j ≡ 0 ( m o d   k ) C_i^j ≡ 0(mod \ k) Cij0(mod k)中符号“ ≡ ≡ ”的意思是同余,这个式子的意思是 C i j C_i^j Cij能整除 k k k
  同余的概念和题目,参考博文:https://blog.csdn.net/weixin_43914593/article/details/107642766
  组合数的定义是: C i j = i ! j ! × ( i − j ) ! C_i^j =\frac{i!}{j!\times(i-j)!} Cij=j!×(ij)!i!
  有多种计算方法。
  (1)直接按定义算。因为有大数,用Python写代码。例如计算 C 50 20 C_{50}^{20} C5020,得47129212243960:

temp = 1   #组合数
i,j = 50,20
for p in range(1,i+1): temp *=p     #求i!
print(temp)
for p in range(1,j+1): temp //=p     #除j!
for p in range(1,i-j+1): temp //=p   #再除以(i-j)!
print(int(temp))

https://blog.csdn.net/qq_36477987/article/details/89521273

  直接算是不好的,因为阶乘增长极快,例如 12 ! = 479 , 001 , 600 12!=479,001,600 12!=479,001,600
(2)按递推式计算: c ( i , j ) = c ( i − 1 , j − 1 ) + c ( i − 1 , j ) c(i,j)=c(i-1,j-1)+c(i-1,j) c(i,j)=c(i1,j1)+c(i1,j)。复杂度 O ( n m ) O(nm) O(nm)。例如计算 C 2000 600 C_{2000}^{600} C2000600

c = [[0 for i in range(2001)] for i in range(2001)] #用于记录组合数c[n][m]
Mod = int(1e9+7)
n,m =2000, 600
for i in range(n+1):
      c[i][0]=1
      c[i][i]=1
for i in range(1,n+1):    #递推计算所有组合数
   for j in range(1,m+1):
         c[i][j]= (c[i-1][j-1]+c[i-1][j])% Mod;
print(c[n][m])

3、小规模代码

   本题有40%的小规模数据, 1 ≤ n , m ≤ 2000 1 ≤ n,m ≤ 2000 1n,m2000,复杂度 O ( n m ) O(nm) O(nm)够用。下面是C++代码,能得到一点分数。

#include<bits/stdc++.h>
using namespace std;
const int Mod = 1e9+7;
int c[2010][2010];  //记录组合数c[n][m]
int main(){
    
    
    int n,m,t,k;
    cin >> t >> k;

    int nn=2000,mm=2000;   //小规模
    for(int i=0;i<=nn;i++){
    
    
        c[i][0]=1;
        c[i][i]=1;
    }
    for (int i =1;i<=nn;i++)   //打表,提前计算出所有组合数
        for(int j =1;j<=mm;j++)
            c[i][j]= (c[i-1][j-1]+c[i-1][j]) % k;  //直接对k取余

    while(t--){
    
    
        cin >> n >> m;
        int ans=0;
        for(int j=0;j<=m;j++)
            for(int i=j;i<=n;i++)
                if(c[i][j] == 0)
                    ans++;
        cout << ans % Mod;
    }
    return 0;
}

4、lucas定理

   当 1 ≤ n , m ≤ 1 0 18 1 ≤ n,m ≤ 10^{18} 1n,m1018时,显然用上面的递推方法计算组合数 C i j C_i^j Cij是不可能的。
   其实学过数论的队员一看本题,就知道它几乎是一道lucas定理的裸题。lucas定理就是求极大的组合数 C n m C_n^m Cnm对p取余。
   因为较为复杂,本文不做解析。请参考博文https://www.freesion.com/article/92161191517/

猜你喜欢

转载自blog.csdn.net/weixin_43914593/article/details/112979712