1. 程式人生 > 實用技巧 >FZU 第十五屆程式設計競賽_重現賽 & FOJ Problem 2289 項鍊

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\) 的情況,考慮倒數第二個位置是否和開頭顏色相同:

  1. 當倒數第二個位置是否和開頭顏色相同時,則前 \((m-2)\) 個位置需滿足相鄰元素顏色不同,且倒數第三個的顏色不與倒數第二個相同,即不與開頭相同。
    \(F_{m-2}\)

    表示 \((m-2)\) 元環中,相鄰元素顏色不同的方案數,與上面的描述等價。故前 \((m-2)\) 的位置的方案數為 \(F_{m-2}\)
    再考慮倒數第二位與開頭顏色相同,方案數為 \(1\) ,最後一位與兩邊相同的顏色不同,方案數為 \((n-1)\) ,故由乘法原理,方案數為 \(F_{m-2}\cdot 1\cdot (n-1)=(n-1)F_{m-2}\)

  2. 當倒數第二個位置是否和開頭顏色不同時,前 \((m-1)\) 個的方案數,即為相鄰元素顏色不同,且倒數第二位顏色不與開頭相同。
    \(F_{m-1}\) 表示 \((m-1)\) 元環中,相鄰元素顏色不同的方案數,與上面的描述等價。故前 \((m-1)\)

    的位置的方案數為 \(F_{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;
}