HEX--之组合数学

Problem Description

On a plain of hexagonal grid, we define a step as one move from the current grid to the lower/lower-left/lower-right grid. For example, we can move from (1,1) to (2,1), (2,2) or (3,2).

In the following graph we give a demonstrate of how this coordinate system works.

Your task is to calculate how many possible ways can you get to grid(A,B) from gird(1,1), where A and B represent the grid is on the B-th position of the A-th line.

Input

For each test case, two integers A (1<=A<=100000) and B (1<=B<=A) are given in a line, process till the end of file, the number of test cases is around 1200.

Output

For each case output one integer in a line, the number of ways to get to the destination MOD 1000000007.

Example Input
1 1
3 2
100000 100000
Example Output
1
3
1
Hint
Author

“浪潮杯”山东省第八届ACM大学生程序设计竞赛(感谢青岛科技大学)

题解:

对于这道题,思考了一阵大约有三种思路,也受别人的启发,确实也对某些问题的分析和规律的查找能够做到有条不紊进行。对于一道问题不能生搬硬套,根据实际问题去摸索,总结遇挫的经验,由于经验不足和盲点的存在以至于对一些问题无从下手。
题目的意思是说给你一个类似于蜂巢型的东西,每一次只能向左走,中间和向下走,给定你(x,y),问你从(1,1)走到(x,y)这个位置有多少情况?
       解法一:本题初来有些无从下手,显然先是对坐标的处理方法(每一个蜂巢上有一对序数对,这些序数对之间存在量的规律变化)有一些敏感,由于此种图形的路径对称可能存在某种坐标转化特殊性,因此寻找坐标之间的桥梁不失为一种可能,稍作分析,发现对于任意一点坐标可以与起始地表示关系为:(x,y)=(1,1)+l*(1,0)+m*(2,1)+r*(1,1)(不包含边界),因此我们将此事进行拆分,得到关于f(x,l,m,r)和f(y,l,m,r)的关系,对自变量的其中之一进行整数的遍历,其它的值也从而确定最后用公式:N=C(x,x+y+z)*C(y,y+z)*C(z,z)种可能性。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
ll fact[101010],inv[101010];

const int mod=1e9+7;
const int maxn=1e5+7;

ll quickM(ll a, ll b){
    ll base = a%mod, ans = 1;
    while(b){
        if(b&1) ans = ans*base%mod;
        base = base*base%mod;
        b >>= 1; 
    }
    return ans;
}
void init(){
    fact[0] = 1;
    for(int i = 1; i <= maxn; ++i)
    fact[i] = fact[i-1]*i%mod;
    inv[maxn]=quickM(fact[maxn],mod-2);
    for(int i=maxn-1;i>=0;i--)
    {
        inv[i]=inv[i+1]*(i+1);
        inv[i]%=mod;
    }
}
ll C(int n, int m){
    return ((fact[n]*inv[m])%mod*(inv[n-m]))%mod;
}

int main()
{
    int a,b;
    init();
    while(cin>>a>>b)
    {
        long long res=0;
        int n=min(a-b,b-1);
        for(ll y=0;y<=min(a-b,b-1);y++)
        {
            ll z=b-1-y;
            ll x=a-b-y;
            res+=(C(z+y+x,x)*C(z+y,y))%mod;
            res%=mod;
        }
        printf("%lld\n",res );
    }

解法二:

   解题思路

我们通过观察可以看得出来从(1,1)到(A,B)有两种主要的方式

方式一:只走左下和右下

方式二:垂直向下,左下,右下都走

这里用一个例子来展开

我们假设所求为(1,1)到(5,3)的路径数


我们可以看到,我们用方式一:需要走两次左下和两次右下,方式二:1,一次左下一次右下,一次垂直向下  2,两次垂直向下

  通过比较方式一和方式二我们发现了一件有趣的事,一次垂直向下操作相当于一次左下加一次右下操作

这里我们先来讨论方式一,方式一的路径数是

 

l是左下的次数,r是右下的次数,A代表全排列也就是阶乘

为什么要采用这个公式呢?这个可以看成是一个排列组合的问题,先把所有的步骤全排列然后去掉其中重复的组合数(高中知识,如果还没看懂,可以问一下同学或者百度)


方式二的求法只要枚举就可以了,枚举方式是,每次left--,right--,down++,这个时候的公式就变成了


枚举到right或者left其中一个为0,这就代表没有办法再抵消成一个down了

因为本题要求我们mod1000000007,所以就想到了逆元

逆元不会的话自行学习吧,这里主要讲解这一题的思路

#include<iostream>  
#include<cstdio>  
using namespace std;  
#define LL long long  
const int fcnt=200005;  
const LL mod=1000000000+7;  
LL fac[fcnt];  
LL inv[fcnt];  
void getfac()  
{  
    fac[0]=1;  
    for(int i=1;i<fcnt;i++)  
        fac[i]=fac[i-1]*i%mod;  
}  
LL quickpow(LL a,LL b)  
{  
    if(b<0) return 0;  
    LL ret=1;  
    a%=mod;  
    while(b)  
    {  
        if(b&1)  
            ret=(ret*a)%mod;  
        b>>=1;  
        a=(a*a)%mod;  
    }  
    return ret;  
}  
void getinv()  
{  
    inv[0]=1;  
    for(int i=1;i<fcnt;i++)  
        inv[i]=quickpow(fac[i],mod-2);  
}  
int main()  
{  
  LL a,b;  
  getfac();  
  getinv();  
  while(scanf("%lld%lld",&a,&b)!=EOF)  
  {  
      LL right=b-1;  
      LL left=a-b;  
      LL down=0;  
      LL sum=0;  
      while(right>=0&&left>=0)  
      {  
          sum=(sum+(((fac[right+left+down]*inv[right])%mod*inv[left])%mod*inv[down])%mod)%mod;  
          right--;left--;down++;  
      }  
      printf("%lld\n",sum);  
  }  
  return 0;  
}  
解法三:

对于解法三,队友对我有点启发,虽然我之前也是这样子想过,这种思路也是容易想的:对于某一点假定其坐标(某种意义上的,虽然有一些不规则)C[m,n](以数组的形式储存着从(1,1)到(m,n)的步数),那么我们将此进一步的向上追溯(有点类似于动态规划),对于C[m,n]的步数来源于从其左边和右边的步数之和,这样子不断将问题进行简化,只是需要考虑边界的状态即可,但本题开一个100000的数组有点让我担心,但是进行记忆化数组的存储可以进行优化,本题只提供思路,不贴代码。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值