P2822组合数问题

组合数问题(NOIP2016提高组Day2T1)

Time Limit:1000MS  Memory Limit:512000K

【题目描述】

组合数表示的是从n个物品中选出m个物品的方案数。举个例子,从(1,2,3) 三个物品中选择两个物品可以有(1,2),(1,3),(2,3)这三种选择方法。根据组合数的定 义,我们可以给出计算组合数的一般公式:



小葱想知道如果给定n,m和k,对于所有的0<=i<= n,0<=j<= min(i,m)有多少对 (i,j)满足是k的倍数。

【输入格式】

第一行有两个整数t,k,其中t代表该测试点总共有多少组测试数据,k的意义见【问题描述】。
接下来t行每行两个整数n,m,其中n,m的意义见【问题描述】。

【输出格式】

t行,每行一个整数代表答案。

【输入样例1】

1 2
3 3

【输出样例1】

1

【输入样例2】

2 5
4 5
6 7

【输出样例2】

0
7

【数据范围】

来一波数学讲解

 

 首先 来一个非常神奇的杨辉三角

 

那么首先我们知道

也就是说杨辉三角上的数都是组合数

第i行第j列就是C(i,j)

C(n,m)=C(n-1,m)+C(n-1,m-1)

这个公式竟然就是杨辉三角的递推式!

也就是说我们就是在杨辉三角上寻找Cij是k的倍数!!!

那么我们可以先一波预处理求出来杨辉三角

and then? 突然之间,好像自己又蒙了。。。

不急!

 

 

我们不妨再重新分析一下

Cij 就是在杨辉三角上第i行第j列的那个数

我们只需要把杨辉三角全扫一遍!?

但是题目要查询多组数据

最大要查询10^4次,这就很尴尬了,不太好整!如果我们每一次都扫一遍,那这简直就是天方夜谭!

我们这里用了一个非常神奇的东西:二维前缀和优化

我们可以把一个矩形表示出来i行j列(其实更像一个平行四边形) 用一个二维的前缀和数组来求往上的区间里的所有的可行解

这样先预处理出来,每次查询直接输出答案就行啦

 

还有一个要注意的要多模几次k!


#include<bits/stdc++.h>
using namespace std;
int C[2005][2005];//杨辉三角组合数数组 
int ans[2005][2005];
int main()
{
    int t,k;
    cin>>t>>k;
    C[1][1]=1;
    for(int i=0;i<=2000;i++) 
        C[i][0]=1;
    for(int i=2;i<=2000;i++){
        for(int j=1;j<=i;j++)
            C[i][j]=(C[i-1][j-1]+C[i-1][j])%k;
    }
    for(int i=2;i<=2000;i++){
        for(int j=1;j<=i;j++){
            ans[i][j]=ans[i-1][j]+ans[i][j-1]-ans[i-1][j-1];
            if(C[i][j]==0)//是k的倍数
                ans[i][j]++; 
        }
        ans[i][i+1]=ans[i][i];
    }
/*    for(int i=1;i<=20;i++){
        for(int j=1;j<=i;j++)
            cout<<ans[i][j]<<" ";
        cout<<endl;
    }
    cout<<endl;
    for(int i=1;i<=20;i++){
        for(int j=1;j<=i;j++)
            cout<<C[i][j]<<" ";
        cout<<endl;
    }*/
    while(t--){
        int n,m;
        cin>>n>>m;
        if(n<m)
            m=n;
        cout<<ans[n][m]<<endl;
    }
    return 0;
}
原文地址:https://www.cnblogs.com/Tidoblogs/p/11219512.html