良い復習になった。
https://yukicoder.me/problems/no/1728
問題
正N角柱がある。底面・上面の1辺の長さと、側面の辺の長さは異なる。
この角柱を成す2N個の頂点のそれぞれを、C色のいずれかで彩色する。
回転して同じ彩色になるケースを重複せず数えるとき、彩色の仕方は何通りか。
解法
ポリアの数え上げ定理の典型例である。
ある頂点が、他の頂点の場所に行くように回転するやり方は2N通りある。
それぞれの回転パターンにおいて、回転前後で同じ彩色になるような塗り分け方を列挙しよう。
ポリアの数え上げ定理より、その2N通りの平均を取れば解となる。
- 上面・底面の中心を通る軸で回転させるケース
- 点vが、wだけ時計回りに回転した位置に来るよう回転させるとする。
- そのためには、GCD(N,w)だけ回転した場合に頂点の色が一致しなければならない。
- この場合、彩色の仕方はC^(2*GCD(N,w))通りある(2倍するのは上面と底面はそれぞれ別の彩色をできるため)。
- wをN通り計算すると間に合わないので、約数包除の要領で数え上げよう。
- 上面と底面が反転するように回転させるケース
- 上面の点vが、底面の同じ位置からw個だけ時計回りに回転した位置に来るよう回転させるとする。
- この回転を2回行うと各点元の位置に戻るので、これらが同じ彩色になるためには、N組の頂点対が同じ色を持てばよい。
- よって彩色の仕方はC^N通り。wがN通りあることを考えると、全体でN*C^N通り。
int T,N,C; const ll mo=1000000007; ll modpow(ll a, ll n = mo-2) { ll r=1;a%=mo; while(n) r=r*((n%2)?a:1)%mo,a=a*a%mo,n>>=1; return r; } vector<ll> enumdiv(ll n) { vector<ll> S; for(ll i=1;i*i<=n;i++) if(n%i==0) {S.push_back(i); if(i*i!=n) S.push_back(n/i); } sort(S.begin(),S.end()); return S; } ll A[4040]; void solve() { int i,j,k,l,r,x,y; string s; cin>>T; while(T--) { cin>>N>>C; auto D=enumdiv(N); ll ret=0; // 上下回転なし for(i=D.size()-1;i>=0;i--) { A[i]=N/D[i]; for(j=i+1;j<D.size();j++) if(D[j]%D[i]==0) A[i]-=A[j]; (ret+=A[i]*modpow(C,2*D[i]))%=mo; } // 上下回転 (ret+=N*modpow(C,N))%=mo; //平均で割る ret=ret*modpow(2*N)%mo; cout<<ret<<endl; } }
まとめ
いつもこの数え上げ混乱する。