【BZOJ3907】网格 组合数,补集转换

来源:互联网 发布:淘宝联盟佣金是全店吗 编辑:程序博客网 时间:2024/05/22 07:51

链接:

#include <stdio.h>int main(){    puts("转载请注明出处[vmurder]谢谢");    puts("网址:blog.csdn.net/vmurder/article/details/44944381");}

题解:

      首先从 (0,0)(n,m) 是右走 n 步, 上走 m 步。方案数是在 n 个数中 n+1 个空中插 m 个数,组合数是Cmn+m
      然后从中减去穿过 y=x 这条线的那些方案数就好了。

      但是这个很难求,而若要求 [ 不经过 ] 则就可以有特别的技巧了。
      假设第一步向上,那么最终无论如何都要经过这条线,因为向上已经走一步,所以需要去掉的方案数则为 Cm1n+m1
      然后如果第一步向右,则若一个方案从起点到最后一次经过 y=x 这条线的那个点的整个路径都沿 y=x 这条直线翻转,剩余到终点 (n,m) 的路径不进行变动,则正好对应唯一一种第一步向上走的方案。而这种情况的方案数则为 Cn1n+m1
      所以求不经过,答案则是 Cmn+m2Cm1n+m1

      那么我们怎么转化成不穿过呢?
      观察发现第一步向上,则定然不符合要求需要被删掉;而第一步向右,满足要求的所有方案,因为无法经过 y=x 这条直线,所以它们被一条无形的线 y=x1 禁锢着,只要越过这条线就会碰触到 y=x 。所以 [ 不经过 ] 情况下的询问 (n,m) 正好对应着 [ 不穿过 ] 情况下的询问 (n1,m)

所以最终答案是 Cmn+m+12Cm1n+m

特殊的骗分技巧:

n==m时答案是卡特兰数哦骚年~
这个可以打表发现!

代码:

def Fac(n):    ans=1    for i in range(2,n+1):        ans=ans*i    return ansdef C(n,m):    if (n<m):        return 0    return (Fac(n)/Fac(m)/Fac(n-m))n,m=raw_input().split()n=int(n)m=int(m)print C(n+m+1,m)-2*C(n+m,m-1)
0 0
原创粉丝点击