かなり苦戦したけどようやく解けた…。
http://arc012.contest.atcoder.jp/tasks/arc012_4
問題
N人の人間が2次元座標状上に存在し、時間1ごとに上下左右のいずれか1マス動く。
各人の座標が与えられたとき、時間T後に全員が原点に戻る場合の数を答えよ。
なお、数が大きくなる場合があるので、moduloで割った余りの数を答える。
解法
下記Σを外す処理がわからなかったので下記サイトを参考にしました。
External Memory ARC #012 D : Don't worry. Be Together
ARC12 D - Don’t worry. Be Together - math314の日記
まず各人が座標(x,y)にいるとすると、その人が原点に戻る場合の数を考える。
これをN人分掛け合わせればよい。
x,yがいずれも正であるとして、この人が時間T後に原点にいるには以下を満たさなければならない。
- x+y <= T
- T-(x+y)が偶数
前者はわかりやすく、x+y>Tだと原点に戻れないので明らか。
後者は、最短では時間(x+y)で原点に戻れるので残りの時間分T-(x+y)余裕があるが、これが奇数だと余分x,y方向に移動した分戻れないので、偶数でなければならない。
ここで、T-(x+y)=2mとする。
(x,y)から(0,0)に最短で向かう場合x回左、y回下に行くわけだが、この場合2m分余分に時間があるのでm回右または上に移動し、同じ回数余分に左または下に移動しても原点に戻れる。
よって、m回のうち右に移動する回数をi回とすると、上に移動する回数が(m-i)回なので全組み合わせはとなる。
ここで、このΣが邪魔なので外すことを考える。
、で、かつであることを考えると、先のΣの式はと変形できる。
ここで、このΣの中を考えると、(x+m)個から(m-i)個選び、(T-(x+m))個からi個選ぶ、という処理を各iに対して行うということは、結局T個からm個選ぶということなるので、この式は
ここで、これらの階乗処理を毎回行うのは重いので、後でまとめて行う。
N人それぞれに対して、各数の階乗が分子にくる回数を累積し、分母にくる数を減算する。
x!を掛け合わせる数をとすると、を求めればよい。
あいにく、は負になることもあり、除算が発生することもある。
よって、まずimos法により、を累積して各数を掛ける回数をもとめる。(になる)
また、Xが合成数の場合、その数を約数に加算する。たとえばの分はとに加算する。
後は各素数yに対して、を掛け合わせればよい。
int N,T; ll MO; int nt; int num[1000003]; int numt[1000003]; ll modpow(ll A,ll B,ll M) { // A^B mod M ll res=1; ll t=A; while(B) { if(B%2==1) res=(res*t) % M; t=(t*t) % M; B>>=1; } return res; } void solve() { int r,i,j,k,l; int x,y,mx,my; GET2(&N,&T); MO=GETi(); FOR(i,N) { int x=abs(GETi()); int y=abs(GETi()); if(x+y>T || (T-(x+y))%2) { _P("0\n"); return; } int m=(T-(x+y))/2; num[x+m]--; num[T-(x+m)]--; num[m]--; num[T-m]--; num[T]+=2; } numt[1000002]=num[1000002]; for(i=1000001;i>=0;i--) numt[i]=numt[i+1]+num[i]; for(i=1000001;i>=2;i--) { if(numt[i]==0) continue; for(k=2;k*k<=i;k++) { if(i%k==0) { numt[i/k]+=numt[i]; numt[k]+=numt[i]; numt[i]=0; break; } } } ll res=1; for(i=2;i<1000003;i++) if(numt[i]) res=(res*modpow(i,numt[i],MO))%MO; _P("%lld\n",res); return; }
まとめ
他人のコードを読んでも、上記の式でΣを外せることがなかなかわからなかった。
Combinationの特性はもうちょい勉強した方がいいな。
合成数のmoduloに大して階乗とかCombinationの計算をするのは初めてだが、幸いそこはすんなり書けた。
この知見は今後も役に立ちそうだ。