JZOJ.3431【GDOI2014模拟】网格 解题报告

网格

题目描述

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

样例输入

6 6 3

样例输出

132 132

数据范围

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

题解

关于这种不能穿过某条直线的网格行走问题,先讲一下怎么做。
我们知道,答案就等于走到点( n n ,m)的所有路径数量减去穿过这条直线的路径数量。
我们还知道由点( 0 0 ,0)走到点( n n ,m)的路径数为 Cn(m)n+m C n + m n ( m )

所以我们现在只需求得穿过这条直线的路径数量就可以知道答案了。
对于一个如下的网格,有这样的一条违法路径穿过了 y y =x这条直线。

这里写图片描述

易得,不能穿过 y y =x这条直线就等于不能碰到 y y =x+ 1 1 ,所以我们找到y= x x +1这条直线(图中为棕色直线),并将原路径沿这条直线对称过去(除了最下面的一段,图中为橙色),可以得到下图

这里写图片描述

像这样,路径中点 A A (n, m m )会对称到点B( m m -1, n n +1),并且从原点走到对称点B(m-1,n+1)的一条路径都可以对称回来,并对应着一条从原点走到终点 A A (n, m m )且穿过y= x x 直线的路径。

所以穿过直线y= x x 的路径数就对于从原点走到对称点的路径数。
走到对称点B( m m -1, n n +1)的路径数 SB S B = Cm1n+1+m1 C n + 1 + m − 1 m − 1 = Cm1n+m C n + m m − 1
走到原终点A(n,m)的路径数 SA S A = Cmn+m C n + m m

Ans A n s = SA S A - SB S B
= Cmn+m C n + m m - Cm1n+m C n + m m − 1

= (n+m)!m!n! ( n + m ) ! m ! ∗ n ! (n+m)!(m1)!(n+1)!

= (n+m)!(n+1)m!(n+1)! ( n + m ) ! ∗ ( n + 1 ) m ! ∗ ( n + 1 ) ! (n+m)!mm!(n+1)!

= (n+m)!(n+1m)m!(n+1)! ( n + m ) ! ∗ ( n + 1 − m ) m ! ∗ ( n + 1 ) !
再约一下分,得

Ans A n s = (n+2)(n+3)(n+4)......(n+m)(n+1m)m! ( n + 2 ) ∗ ( n + 3 ) ∗ ( n + 4 ) ∗ . . . . . . ∗ ( n + m ) ∗ ( n + 1 − m ) m !

分数线上面的部分我们可以用高精度乘法将积算出来。
那分母怎么处理呢?
我们看到数据范围,惊奇的发现 m m <=5000,于是我们可以打一个单精度除法除 m m <script type="math/tex" id="MathJax-Element-85">m</script>次。
因为直接打会超时,所以打高精度时要压位。

Code(Pascal)

const
    mo=100000000000000;
var
    lj,dt:array[0..30000] of int64;
    n,m,j,i,l:longint;
    k,o:int64;
procedure cs(o:int64);
    var
        i,j,l:longint;
        hhh:int64;
    begin
        hhh:=0;
        for i:=1 to lj[0] do
        begin
            dt[i]:=lj[i]*o+hhh;
            hhh:=dt[i] div mo;
            dt[i]:=dt[i] mod mo;
        end;
        dt[0]:=lj[0];
        while hhh>0 do
        begin
            inc(dt[0]);
            dt[dt[0]]:=hhh mod mo;
            hhh:=hhh div mo;
        end;
        for i:=0 to dt[0] do
        lj[i]:=dt[i];
    end;
procedure cd(o:int64);
    var
        i,j,l:longint;
        hhh:int64;
    begin
        for i:=lj[0] downto 1 do
        begin
            dt[i]:=(lj[i]+hhh) div o;
            hhh:=(lj[i]-dt[i]*o+hhh)*mo;
        end;
        dt[0]:=lj[0];
        while (dt[dt[0]]=0) and (dt[0]>0) do
        dec(dt[0]);
        for i:=lj[0] downto dt[0] do
        lj[i]:=0;
        for i:=0 to dt[0] do
        lj[i]:=dt[i];
    end;
begin
    readln(n,m);
    lj[0]:=1;
    lj[1]:=1;
    for i:=n+2 to n+m do
    cs(i);
    cs(n+1-m);
    for i:=1 to m do
    cd(i);
    write(lj[lj[0]]);
    for i:=lj[0]-1 downto 1 do
    begin
        k:=lj[i];
        o:=0;
        while k>0 do
        begin
            inc(o);
            k:=k div 10;
        end;
        for l:=1 to 14-o do
        write(0);
        write(lj[i]);
    end;
end.
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值