3431. 【GDOI2014模拟】网格

12 篇文章 0 订阅
2 篇文章 0 订阅

Description

某城市的街道呈网格状,左下角坐标为A(0, 0),右上角坐标为B(n, m),其中n >= m。现在从A(0, 0)点出发,只能沿着街道向正右方或者正上方行走,且不能经过图示中直线左上方的点,即任何途径的点(x, y)都要满足x >= y,请问在这些前提下,到达B(n, m)有多少种走法。

Input

输入文件中仅有一行,包含两个整数n和m,表示城市街区的规模。

Output

输出文件中仅有一个整数和一个换行/回车符,表示不同的方案总数。

Sample Input

输入1:

6 6

输入2:

5 3

Sample Output

输出1:

132

输出2:

28

Data Constraint

50%的数据中,n = m,在另外的50%数据中,有30%的数据:1 <= m < n <= 100

100%的数据中,1 <= m <= n <= 5 000

Solution

对于n=m的情况:,即为卡特兰数的第n项。

因为必须有n步向上,m步向右。

向右的个数大于等于向上的个数,把向右看成左括号,向上看成右括号,即为括号序问题,答案为卡特兰数的第n项。

通项公式为f[ n ]=C(2n,n)-C(2n,n+1)。

对于n=m或者n!=m的情况:

考虑Ans=从(0,0)走到(n,m)的总方案数-不合法方案数。

从(0,0)走到(n,m)一共要向右走n次,向上走m次,一共n+m次,任意选出n次向右,其他向上,总方案数为C(n+m,n)。

考虑不合法方案数,即 穿过 直线y=x的路线的方案数。

考虑 穿过 直线y=x,就相当于碰到(即某一点走到或穿过直线上)直线y=x+1的路径的方案数。

那么将从起点(0,0)开始的路径中,最后一个与直线y=x+1接触的点之前的路径全部关于y=x+1对称过去,就得到了一个从(-1,1)开始的走到(n,m)的只能向上或向右走的路径,每一条路径都可以还原成一个从起点(0,0)开始的不合法的路径。

其方案数为C(n-(-1)+m-1,n-(-1))=C(n+m,n+1)。

答案为C(n+m,n)-C(n+m,n+1)。

考虑转化为阶乘,对乘积进行质因数分解,除法相当于直接将指数相减,最后做一次高精度乘法和高精度减法即可。

Code

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define I int
#define ll long long
#define F(i,a,b) for(ll i=a;i<=b;i++)
#define Fd(i,a,b) for(I i=a;i>=b;i--)
#define mem(a,b) memset(a,b,sizeof a)
#define M 1000000000
using namespace std;
I n,m,p[10010],bz[10010],f[10010],now;
ll a[10010],b[10010],c[10010],r;
I main(){
	freopen("grid.in","r",stdin);
	freopen("grid.out","w",stdout);
	scanf("%d%d",&n,&m);
	F(i,2,10000){
		if(!bz[i]) p[++p[0]]=i;
		F(j,1,p[0]){
			if(i*p[j]>10000) break;
			bz[i*p[j]]=1;
			if(i%p[j]==0) break;
		}
	}
	mem(f,0);
	F(i,1,p[0]){
		now=n+m;
		if(p[i]>now) break;
		while(now){
			f[i]+=now/p[i];
			now/=p[i];
		}
	}
	F(i,1,p[0]){
		now=n;
		if(p[i]>now) break;
		while(now){
			f[i]-=now/p[i];
			now/=p[i];
		}
	}
	F(i,1,p[0]){
		now=m;
		if(p[i]>now) break;
		while(now){
			f[i]-=now/p[i];
			now/=p[i];
		}
	}
	a[0]=a[1]=1;
	F(i,1,p[0]){
		while(f[i]){
			F(j,1,a[0]) a[j]*=p[i];
			F(j,1,a[0]){
				a[j+1]+=a[j]/M;
				a[j]%=M;
			}
			while(a[a[0]+1]){
				a[0]++;
				a[a[0]+1]+=a[a[0]]/M;
				a[a[0]]%=M;
			}
			f[i]--;
		}
	}
	mem(f,0);
	F(i,1,p[0]){
		now=n+m;
		if(p[i]>now) break;
		while(now){
			f[i]+=now/p[i];
			now/=p[i];
		}
	}
	F(i,1,p[0]){
		now=n+1;
		if(p[i]>now) break;
		while(now){
			f[i]-=now/p[i];
			now/=p[i];
		}
	}
	F(i,1,p[0]){
		now=m-1;
		if(p[i]>now) break;
		while(now){
			f[i]-=now/p[i];
			now/=p[i];
		}
	}
	b[0]=b[1]=1;
	F(i,1,p[0]){
		while(f[i]){
			F(j,1,b[0]) b[j]*=p[i];
			F(j,1,b[0]){
				b[j+1]+=b[j]/M;
				b[j]%=M;
			}
			while(b[b[0]+1]){
				b[0]++;
				b[b[0]+1]+=b[b[0]]/M;
				b[b[0]]%=M;
			}
			f[i]--;
		}
	}
	F(i,1,a[0]) c[i]=a[i]-b[i];
	F(i,1,a[0]) if(c[i]<0){
		c[i]+=M;
		c[i+1]--;
	}
	c[0]=a[0];
	while(!c[c[0]]&&c[0]) c[0]--;
	printf("%d",c[c[0]]);
	Fd(i,c[0]-1,1) printf("%09d",c[i]);
	return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值