您的位置:首页 > 其它

【P2822】组合数问题 2016NOIP提高组 D2T1

2017-10-18 19:25 260 查看
虽然是自己写题解,但是我觉得这个人的题解写的很好玩2333

先丢一份链接23333

http://www.cnblogs.com/OIerShawnZhou/p/7518444.html

描述:

组合数Cmn表示从n个物品中选m个物品的方案数,根据组合数的定义,我们可以知道计算组合数的一般公式:

Cmn=n!/(m!*(n-m)!)

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

输入描述 Input Description

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

接下来t行每行两个整数n, m,其中n, m的意义见【问题描述】。

输出描述 Output Description

t行,每行一个整数代表所有的0<=i<=n,0<=j<=min(i,m)中有多少对(i, j)满足C(j,i)是k的倍数

样例输入 Sample Input

输入1:

1 2

3 3

输入2:

2 5

4 5

6 7

样例输出 Sample Output

输出1:

1

输出2:

0

7

思路:

组合数问题。。。╮(╯_╰)╭ 目前数学上还没学到,,,就是生物学了学组合数才会一点

在这里如果我们打表处理一下的话不难发现这些数的规律是杨辉三角,因此从杨辉三角入手解决这个问题(好像组合数的一个经典解法就是联系杨辉三角来着)

首先说这个 c(i,j) = c(i-1,j) + c(i-1,j-1) 数学选修2-3第二章左右介绍的组合数公式。。。。

n只有2000,和清北的一个题目相比起来已经非常小了(抽空我再把清北那个发上来),所以可以考虑先把这个范围内的所有f[i][j]都先求出来,就用那个公式,全都推出来

边界是f[i][0] = f[i][i] = 1 ←是组合数的性质2333

为了求解方便,在推的时候可以把值对k取模,这样推出来后的f[i][j]如果是0,那么这个就是k的倍数

令s[i][j]表示在所有的c(i,j) (1≤j≤i)的里面,为k的倍数的有多少个,那么处理数组的时候就是s[i][j] = s[i][j-1] ,每找到一个f[i][j]为0就让ans++即可求解

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define RI register int
typedef long long LL;
const int sz =2500;
LL t,n,m,k,ans;
LL f[sz][sz],s[sz][sz];
inline void read(LL &x){
x=0;bool fl=0;
char ch=getchar();
while(ch<'0'||ch>'9')
{if(ch=='-') fl=1;ch=getchar();}
while(ch>='0'&&ch<='9')
{x=x*10+ch-'0';ch=getchar();}
if(fl)
x*=-1;
}
inline void write(LL x){
if(x<0)
putchar('-'),x*=-1;
if(x/10)
write(x/10);
putchar(x%10+'0');
}
int main()
{
read(t),read(k);
for(RI i=0;i<=2333;++i)
{
f[i][i]=1;
f[i][0]=1;
}
for(RI i=1;i<=2333;++i)
for(RI j=1;j<i;++j)
f[i][j]=(f[i-1][j-1]%k+f[i-1][j]%k)%k;
for(RI i=1;i<=2333;++i)
for(int j=1;j<=i;++j)
{
s[i][j]=s[i][j-1];
if(f[i][j]==0)
s[i][j]++;
}
while(t--)
{
read(n),read(m);
ans=0;
for(LL i=1;i<=n;++i)
{
LL j=min(i,m);
ans+=s[i][j];
}
write(ans),puts("");
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: