这道题刚开始我是想用dfs求出所有组合,然后再用给定的Cmn=n!m!(n−m)!求出每个组合的值,再挨个判断是否是k的倍数,但是这样时间复杂度肯定特别高。
优解
用Pascal三角形公式求出所有组合的追,再%k判断是否为k的倍数。
pascal是这样一个图:
2.我们可以利用前缀和思想,s[n][m]就表示n*m矩形中有多少合法方案
code
#include<iostream>
using namespace std;
const int N=2010;
int c[N][N];//组合数组
int s[N][N];//前缀和数组
int main()
{
int T, k;
scanf("%d%d", &T, &k);
//用Pascal三角形公式求出所有组合情况,再%k判断是否为k的倍数
for(int i=0;i<N;i++)//所有行
for(int j=0;j<=i;j++)//从i中选j个
{
if(!j)c[i][j]=1%k;//j=0时即从i个中选0个,是非法情况。但我们是根据%k是否为0来判断是否为k的倍数的,因此j=0会被记作正确答案,为了防止这种情况,我们用1%任何数都为1的特性把j=0情况时%k的值赋为1
else //否则就是合法情况,套用pascal公式求出所有组合情况
c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % k;
if(!c[i][j])s[i][j]=1;//s[i][j]应该等于其左侧元素之和和上方元素之和的和。当c[i][j]为0时,它意味着在计算c[i][j]时,左侧元素c[i-1][j]和上方元素c[i-1][j-1]的和为0。因此,在这种情况下,s[i][j]应该被设置为1,表示该位置的前缀和为0。
}
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
{
if(i)s[i][j]+=s[i-1][j];//如果i=0,即当前不是第一行,那么求把上一行的值加上
if(j)s[i][j]+=s[i][j-1];//如果j=0,即当前不是第一列,就把左边的列加上。
if(i&&j)s[i][j]-=s[i-1][j-1];//当前不是边界,那么就减去左上角的前缀和
}
while(T--)
{
int n,m;
scanf("%d%d",&n,&m);
printf("%d\n",s[n][m]);
}
return 0;
}