题目描述
有一个 n×m(1≤n,m≤300000) n × m ( 1 ≤ n , m ≤ 300000 ) 的网格,求从左上角第一个格子走到右下角最后一个格子的方案数(由于方案数过大,请余除23333333),只能选择向下走或向右走。
算法分析
由于只能向左走或向右走,总步数固定为 n+m−2 n + m − 2 ,向下走的步数固定为 m−1 m − 1 ,则答案就是在 n+m−2 n + m − 2 步中选择 m−1 m − 1 步向下走,其余步数向右走的组合数,即 Cm−1n+m−2 C n + m − 2 m − 1 。
由于模数不为质数,我们只能暴力求解,根据组合数公式将阶乘分解质因数后约分,然后使用快速幂计算结果即可,注意数组开要两倍。
代码实现
#include <cstdio>
#include <cstring>
typedef long long int ll;
const int maxn=600005;
const int p=23333333;
inline int mul(int x,int y) {return (long long int)x*y%p;}
int notprime[maxn],prime[maxn],idx=0;
inline void getPrime(int n) {
memset(notprime,0,sizeof(notprime));idx=0;
for(int i=2;i<=n;++i) {
if(!notprime[i]) prime[idx++]=i;
for(int j=0;j<idx&&(long long int)i*prime[j]<=n;++j) {
notprime[i*prime[j]]=true;
if(i%prime[j]==0) break;
}
}
}
int times[maxn];
inline void divide(int num,int d) {
for(int i=0;i<idx;++i) {
long long int now=prime[i];
while(now<=num) {
times[i]+=num/now*d;
if((long long int)now*prime[i]>num) break;
else now=now*prime[i];
}
}
}
inline int qpow(int n,int k) {
int ans=1;
while(k) {
if(k&1) ans=mul(ans,n);
n=mul(n,n);k>>=1;
}
return ans;
}
inline int C(int n,int k) {
getPrime(n);
divide(n,1);divide(n-k,-1);divide(k,-1);
int ans=1;
for(int i=0;i<idx;++i) ans=mul(ans,qpow(prime[i],times[i]));
return ans;
}
int main() {
freopen("hst.in","r",stdin);freopen("hst.out","w",stdout);
int n,m;scanf("%d%d",&n,&m);
printf("%d\n",C(n+m-2,m-1));
fclose(stdin);fclose(stdout);
return 0;
}