JAG夏合宿2010 Day2 Problem F 10歳の動的計画法

問題

道が碁盤の目状の街において、
家(0,0)から学校(N,M)まで、←または↓への移動をちょうどK回だけして辿り着く方法は何通りあるか。


X座標またはY座標が負になるような点には入ることはできないが、
X座標がNを超える、またはY座標がMを超えるような点に入ることは出来る。

制約条件

N,M≦100000
K≦10000

方針

解説スライドの通りである。
左への移動をi回として、iを0からKまでそれぞれの場合について求め、足し合わせる。
更に、縦の移動と横の移動は独立して考え、それぞれを掛け合わせる。


左にi回移動して、(途中で座標が負にならず)座標Nまで辿り着く場合の数はどうやって求めるか。
これは次のように考える。
N+2i回の右移動とi回の左移動を、条件を満たすように並べ替える場合の数である。
すなわち、碁盤の目において、
(0,0)から(i,N+i)まで、y>xを満たしながら移動する場合の数である。
これはゴールをGとして、Gを直線y=x-1について折り返したような点をG'とすれば、
(Gへ行く場合の数)-(G'へ行く場合の数)で求められる。


それぞれはコンビネーションで計算でき、それは、階乗を事前に計算しておくことで、
O(log n)くらいで計算できる。

ソースコード

invModはspaghetti sourceのコードなので省略。

int fact[MAX];
int N,M,K;
int C(int n,int k){
  return fact[n]*invMod(fact[n-k],mod)%mod*invMod(fact[k],mod)%mod;
}
int calc(int n,int p){
  return C(n+2*p,p)*(n+1ll)%mod*invMod(n+p+1,mod)%mod;
}
int main(){
  cin>>N>>M>>K;
  
  fact[0]=1;
  for(int i=1;i<MAX;i++)fact[i]=(ll)fact[i-1]*i%mod;
  
  int ans=0;
  for(int i=0;i<=K;i++){
    (ans+=(ll)calc(N,i)*calc(M,K-i)%mod*C(N+M+2*K,N+2*i)%mod)%=mod;
  }
  cout<<ans<<endl;
  return 0;
}int fact[MAX];
int N,M,K;
int C(int n,int k){
  return fact[n]*invMod(fact[n-k],mod)%mod*invMod(fact[k],mod)%mod;
}
int calc(int n,int p){
  return C(n+2*p,p)*(n+1ll)%mod*invMod(n+p+1,mod)%mod;
}
int main(){
  cin>>N>>M>>K;
  
  fact[0]=1;
  for(int i=1;i<MAX;i++)fact[i]=(ll)fact[i-1]*i%mod;
  
  int ans=0;
  for(int i=0;i<=K;i++){
    (ans+=(ll)calc(N,i)*calc(M,K-i)%mod*C(N+M+2*K,N+2*i)%mod)%=mod;
  }
  cout<<ans<<endl;
  return 0;
}