FZU 第十五屆程式設計競賽_重現賽 & FOJ Problem 2289 項鍊
FZU 第十五屆程式設計競賽_重現賽 & FOJ Problem 2289 項鍊
給定一個 \(m\) 元環與 \(n\) 種顏色,要求給環的每個元素上色,並且相鄰元素的顏色不同
題為求解 \(n\) 個顏色填塗 \(m\) 元環的方案數
設方案數為 \(F_m\) ,即表示 \(m\) 元環中,相鄰元素顏色不同的方案數
易得 \(F_1=n,F_2=n(n-1),F_3=n(n-1)(n-2)\)
對於 \(n>3\) 的情況,考慮倒數第二個位置是否和開頭顏色相同:
-
當倒數第二個位置是否和開頭顏色相同時,則前 \((m-2)\) 個位置需滿足相鄰元素顏色不同,且倒數第三個的顏色不與倒數第二個相同,即不與開頭相同。
而 \(F_{m-2}\)
再考慮倒數第二位與開頭顏色相同,方案數為 \(1\) ,最後一位與兩邊相同的顏色不同,方案數為 \((n-1)\) ,故由乘法原理,方案數為 \(F_{m-2}\cdot 1\cdot (n-1)=(n-1)F_{m-2}\) -
當倒數第二個位置是否和開頭顏色不同時,前 \((m-1)\) 個的方案數,即為相鄰元素顏色不同,且倒數第二位顏色不與開頭相同。
而 \(F_{m-1}\) 表示 \((m-1)\) 元環中,相鄰元素顏色不同的方案數,與上面的描述等價。故前 \((m-1)\)
再考慮最後一位,其不與兩邊不同顏色相同,故方案數為 \((n-2)\)。故由乘法原理得,方案數為 \((n-2)F_{m-1}\)
最後我們由加法原理得知, \(F_m=(n-2)F_{m-1}+(n-1)F_{m-2},m>3\)
【法一】
\(n=1\) 時特判,否則使用矩陣快速冪:
構造矩陣:\( \left( \begin{matrix} F_m \\\ \\ F_{m-1} \end{matrix} \right)= \left( \begin{matrix} n-2&n-1 \\\ \\ 1&0 \end{matrix} \right)\cdot \left( \begin{matrix} F_{m-1} \\\ \\ F_{m-2} \end{matrix} \right)\)
不難得到 \( \left( \begin{matrix} F_{m+1} \\\ \\ F_m \end{matrix} \right)= \left( \begin{matrix} n-2&n-1 \\\ \\ 1&0 \end{matrix} \right)^{n-2}\cdot \left( \begin{matrix} F_3 \\\ \\ F_2 \end{matrix} \right)\)
由於 \(F_3=n(n-1)(n-2),F_2=n(n-1)\) ,故將中間的矩陣快速冪求出 \((n-2)\) 次方可得到解
【程式碼】
#include<iostream>
using namespace std;
typedef long long ll;
const ll MOD=1e9+7;
ll N,M;
struct Matrix{
ll Num[2][2];
Matrix() { Num[0][0]=Num[0][1]=Num[1][0]=Num[1][1]=0; }
Matrix operator * (const Matrix &x) const{
Matrix y;
for(int i=0;i<2;i++)
for(int j=0;j<2;j++)
for(int k=0;k<2;k++)
y.Num[i][j]=(y.Num[i][j]+Num[i][k]*x.Num[k][j]%MOD)%MOD;
return y;
}
Matrix pow(ll x){
Matrix ans,a=*this;
ans.Num[0][0]=ans.Num[1][1]=1;
for(;x;x>>=1,a=a*a) if(x&1) ans=ans*a;
return ans;
}
};
inline int ans(){
if(M==1) return N;
Matrix ans,vec;
ans.Num[0][0]=N-2;
ans.Num[0][1]=N-1;
ans.Num[1][0]=1;
vec.Num[0][0]=N*(N-1)*(N-2);
vec.Num[1][0]=N*(N-1);
return (ans.pow(M-2)*vec).Num[1][0];
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
while(cin>>N>>M)
cout<<ans()<<endl;
return 0;
}
【法二】
考慮到有些喪心病狂的出題人可能會卡常數(雖然這題顯然不會),考慮一下別的做法:
由遞推式 \(F_m=(n-2)F_{m-1}+(n-1)F_{m-2},m>3\) 可以構造出
\(F_m-(n-1)F_{m-1}=-F_{m-1}+(n-1)F_{m-2}=(-1)^1\cdot [F_{m-1}-(n-1)F_{m-2}],m>3\)
故可得 \(F_m-(n-1)F_{m-1}=(-1)^{m-3}\cdot [F_3-(n-1)F_2],m>3\)
代入可得 \(F_m-(n-1)F_{m-1}=-(-1)^m\cdot [n(n-1)(n-2)-(n-1)n(n-1)]=(-1)^m\cdot n(n-1),m>3\)
移項即可得到 \(F_m=(n-1)F_{m-1}+(-1)^m\cdot n(n-1)=(n-1)[F_{m-1}+(-1)^m\cdot n],m>3\)
故 \(F_m-(-1)^m\cdot (n-1)=(n-1)[F_{m-1}+(-1)^m\cdot n-(-1)^m]=(n-1)[F_{m-1}-(-1)^{m-1}\cdot (n-1)],m>3\)
因此很顯然,數列 \(\{F_m-(-1)^m\cdot (n-1)\},m>3\) 是一個等比數列
進而得到 \(F_m-(-1)^m\cdot (n-1)=(n-1)^{m-2}[F_2-(-1)^2\cdot (n-1)]\)
代入 \(F_2=n(n-1)\) 整理得到 \(F_m=(-1)^m\cdot (n-1)+(n-1)^m,m>3\)
式子推出來了,剩下的快速冪即可過
【程式碼】
#include<iostream>
using namespace std;
typedef long long ll;
const int MOD=1e9+7;
inline ll fpow(ll a,ll x){
ll ans=1;
for(;x;x>>=1,a=a*a%MOD) if(x&1) ans=ans*a%MOD;
return (ans+MOD)%MOD;
}
inline ll ans(ll N,ll M){
if(M==1) return N;
if(M==2) return N*(N-1);
return (fpow(-1,M)*(N-1)+fpow(N-1,M))%MOD;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
ll N,M;
while(cin>>N>>M)
cout<<ans(N,M)<<endl;
return 0;
}