题目大意:给出上述图形外围的小球的个数,用k种颜色给小球即中间的大球染色,要求有边相连得不能染相同的颜色。求本质不同的染色方案(旋转后相同算是本质相同。)
题解:置换+DP+矩阵乘法
置换f下保持不变的着色方案个数C(f)=k^m(f) m(f)为循环的个数。在置换下保持不变,说明轮换中的点都染了相同的颜色。
之前提到过如果是一个n元环,旋转i,那么循环的个数为gcd(n,i),循环的长度为n/gcd(n,i)
那么我们先假设可以求出f[i]表示i个轮换的染色方案数。
那么答案就是∑(i=1..n)f[gcd(n,i)]。
上面的式子枚举的时间复杂度很高,所以我们考虑不枚举i,枚举循环的长度L.
枚举了L,在计算有多少个i是0<=i<=n并且使L=n/gcd(n,i),即gcd(n,i)=n/L
不妨设a=n/L=gcd(n,i),i=a*t则当且仅当gcd(L,t)=1时,gcd(a*L,a*t)=gcd(n,i)=a
因为0<=i<=n,所以0<=t<=(n/a=L),所以满足条件的t的个数就是phi(L)欧拉函数。
那么∑(i=1..n)f[gcd(n,i)]=∑(L|n)f[n/L]*phi(L)。可以在O(sqrt(n))的时间求解
那么现在的问题就是如何快速的求f。
虽然是i个轮换,其实我们完全可以看成是i个球连成了一个环。相邻的两个颜色不能相同。加入一个轮换相当于在n,1之间插入一个球,那么不能与1,n的颜色相同所以有(k-2)种选择。因为加入一个球的缘故使n与1从原先的不能染相同的颜色变成了可以染相同的颜色,那么我们默认n,1的颜色相同就有(k-1)种旋转。
f[1]=0 f[2]=k*(k-1) f[i]=f[i-1]*(k-2)+f[i-2]*(k-1) 这个式子的话可以用矩阵乘法优化。具体的矩阵可以看程序。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define N 10000003 #define p 1000000007 #define LL long long using namespace std; int pd[N],prime[N],phi[N],f[3],n; LL k; struct data{ LL a[3][3]; }ch,e,b; void init(int n) { phi[1]=1; for (int i=2;i<=n;i++) { if (!pd[i]) { prime[++prime[0]]=i; phi[i]=i-1; } for (int j=1;j<=prime[0];j++) { if(prime[j]*i>n) break; pd[i*prime[j]]=1; if (i%prime[j]==0) { phi[i*prime[j]]=phi[i]*prime[j]; break; } else phi[i*prime[j]]=phi[i]*(prime[j]-1); } } } LL getphi(int x) { if (x<=1000000) return (LL)phi[x]; int ans=x; for (int i=1;prime[i]*prime[i]<=x;i++) if (x%prime[i]==0) { ans=ans/prime[i]*(prime[i]-1); while (x%prime[i]==0) x/=prime[i]; } if(x>1) ans=ans/x*(x-1); return (LL)ans%p; } void clear(data &a) { for (int i=1;i<=2;i++) for (int j=1;j<=2;j++) a.a[i][j]=e.a[i][j]; } data mul(data a,data b) { data c; for (int i=1;i<=2;i++) for (int j=1;j<=2;j++) { c.a[i][j]=0; for (int k=1;k<=2;k++) c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j])%p; } return c; } data quickpow(data num,int x) { data ans; clear(ans); data base; base=num; while (x) { if (x&1) ans=mul(ans,base); x>>=1; base=mul(base,base); } return ans; } LL calc(int i) { if (i<=2) return f[i]; data t=quickpow(ch,i-2); t=mul(b,t); return t.a[1][2]; } LL quickpow(LL num,int x) { LL ans=1; LL base=num%p; while (x) { if (x&1) ans=ans*base%p; x>>=1; base=base*base%p; } return ans; } int main() { freopen("a.in","r",stdin); init(1000000); ch.a[1][1]=0; ch.a[1][2]=1; ch.a[2][1]=1; ch.a[2][2]=1; for (int i=1;i<=2;i++) e.a[i][i]=1; while (scanf("%d%I64d",&n,&k)!=EOF) { k--; f[1]=0; f[2]=k*(k-1)%p; b.a[1][1]=f[1]; b.a[1][2]=f[2]; b.a[2][1]=0; b.a[2][2]=0; ch.a[1][1]=0; ch.a[2][1]=1; ch.a[1][2]=k-1; ch.a[2][2]=k-2; LL ans=0; for (int i=1;i*i<=n;i++) if (n%i==0){ LL t=calc(n/i); ans+=(LL)t*getphi(i); ans%=p; if (i*i!=n) { LL t=calc(i); ans+=(LL)t*getphi(n/i); } ans%=p; } ans=ans*(k+1)%p*quickpow(n,p-2)%p; printf("%I64d\n",ans); } }