考え方はわかっても数え上げは大変。
https://yukicoder.me/problems/no/3123
問題
整数Nが与えられる。
1~NのPermutation Pに対し、f(P)は以下により得られる異なる数列の数とする。
- Pを、P[R[i]]=iを満たす数列Rに置き換える
- Pを、R[i]=P[N-1-i]を満たす数列Rに置き換える。
PはN!通りあるが、それぞれf(P)の総和を求めよ。
解法
Pに対応する行列Aとして、P[i]=jならA[i][j]=1であるようなものを考える。
前者の処理はAを90度回転、後者の処理はAを線対称反転することに対応する。
N=1の場合を除くと、両操作の繰り返しにより得られるf(P)は2,4,8のいずれかとなる。
それぞれ数え上げよう。
- f(P)=2となるケース
- 90度回転に対し変化せず、線対称ではないもの
- 90度回転かつ反転すると元に戻るもの
- f(P)=4となるケース
- 180度回転すると元に戻り、かつ線対称ではないもの
- 対角線に対称で、90度・180度回転だけではもとに戻らないケース
- f(P)=8となるケース
- 上記以外
f(P)=2、f(P)=4となるケースは、二重階乗や三項間の漸化式により求められる。
int T; ll mo; int N; ll p2[5<<20]; ll fact[5<<20]; ll fact2[5<<20]; ll dp2[5<<20]; ll dp3[5<<20]; ll dp4[5<<20]; ll dp5[5<<20]; ll dp6[5<<20]; ll dp7[5<<20]; ll ret[5<<20]; void solve() { int i,j,k,l,r,x,y; string s; cin>>T>>mo; p2[0]=fact[0]=fact2[0]=fact2[1]=1%mo; dp5[1]=1; dp5[2]=2; for(i=1;i<(5<<20);i++) { p2[i]=p2[i-1]*2%mo; fact[i]=fact[i-1]*i%mo; ret[i]=fact[i]; if(i==1) continue; fact2[i]=fact2[i-2]*i%mo; // 90度回転対称 if(i%4==0||i%4==1) dp2[i]=p2[i/4]*fact2[(i-2)/2]%mo; if(i<=3) dp3[i]=2%mo; else if(i<=5) dp3[i]=6%mo; else if(i%2==0) dp3[i]=(2*dp3[i-2]+(i-2)*dp3[i-4])%mo; else dp3[i]=(2*dp3[i-2]+(i-3)*dp3[i-4])%mo; if(i%2==0) dp4[i]=(fact2[i]-dp3[i]-dp2[i])%mo; else dp4[i]=(fact2[i-1]-dp3[i]-dp2[i])%mo; if(i>=3) dp5[i]=(dp5[i-1]+(i-1)*dp5[i-2])%mo; ll c5=dp5[i]-dp3[i]; dp7[i]=fact[i]-(dp2[i]+dp3[i]+dp4[i]+c5+c5); ret[i]=(dp7[i]*8+dp2[i]*2+dp3[i]*2+dp4[i]*4+c5*4+c5*4)%mo; ret[i]=(ret[i]+mo)%mo; } while(T--) { cin>>N; cout<<ret[N]<<endl; } }
まとめ
考え方はわかっても、すんなり式が立たなさそう。