kmjp's blog

競技プログラミング参加記です

yukicoder : No.1728 [Cherry 3rd Tune] Bullet

良い復習になった。
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;
	}
}

まとめ

いつもこの数え上げ混乱する。