題目鏈接
https://www.luogu.org/problem/P1357
題目描述
小L有一座環形花園,沿花園的順時針方向,他把各個花圃編號爲1~N(2<=N<=10^15)。他的環形花園每天都會換一個新花樣,但他的花園都不外乎一個規則,任意相鄰M(2<=M<=5,M<=N)個花圃中有不超過K(1<=K<M)個C形的花圃,其餘花圃均爲P形的花圃。
例如,N=10,M=5,K=3。則
CCPCPPPPCC 是一種不符合規則的花圃;
CCPPPPCPCP 是一種符合規則的花圃。
請幫小L求出符合規則的花園種數Mod 1000000007
由於請您編寫一個程序解決此題。
輸入格式
一行,三個數N,M,K。
輸出格式
花園種數Mod 1000000007
輸入輸出樣例
輸入 #1
10 5 3
輸出 #1
458
輸入 #2
6 2 1
輸出 #2
18
說明/提示
【數據規模】
40%的數據中,N<=20;
60%的數據中,M=2;
80%的數據中,N<=10^5。
100%的數據中,N<=10^15。
思路
假如花園是一條鏈,設f[i][j]爲在有i個花圃的情況下,最後m個花圃狀態爲j的花園種數。其中j爲狀態壓縮下的二進制數,共有m位,1表示該位爲C形花圃。
轉移方程:
ll t1=j>>1,t2=(j>>1)|(1<<(m-1));
if(num_1(t2)<=k){//1的個數小於k
f[i][j]=f[i-1][t1]+f[i-1][t2];
}
else{
f[i][j]=f[i-1][t1];
}
舉個例子,如果m=5,當前狀態爲10010,它可能是由狀態01001或11001轉移而來,如果都滿足k的限制的話。
接下來,要考慮到花園是環形的。
換個角度想,一個有n個花圃環形花園與一個有n+m個花圃且前m個花圃與後m個花圃完全相同的鏈狀花園的種類數是一樣的。
假設前m個花圃的狀態爲j,那麼我們先將所有f賦值爲零,令f[m][j]=1,然後從f[m+1][...]開始計算,最終答案爲f[n+m][j]。
枚舉所有初始狀態,求和得到最後結果。
由於n的值會很大,但遞推式不復雜,可以採用矩陣快速冪優化算法。
代碼
#include <bits/stdc++.h>
#define ll long long
#define mod 1000000007
using namespace std;
ll n,m,k,len,f[101],y[100];
inline ll num_1(ll st){
ll ans=0;
while(st){
if(st&1==1)ans++;
st=(st>>1);
}
return ans;
}
struct Mat{
ll mt[101][101];
Mat(){
memset(mt,0,sizeof(mt));
}
};
Mat a,e;
Mat Mul(Mat x,Mat y) //矩陣乘
{
Mat c;
for(ll i=1;i<=len;i++)
for(ll j=1;j<=len;j++)
c.mt[i][j]=0;
for(ll i=1;i<=len;i++)
for(ll j=1;j<=len;j++)
for(ll k=1;k<=len;k++)
c.mt[i][j]=c.mt[i][j]%mod+x.mt[i][k]*y.mt[k][j]%mod;
return c;
}
Mat pow(Mat x,ll y) //矩陣快速冪
{
Mat ans=e;
while(y)
{
if(y&1)
ans=Mul(ans,x);
x=Mul(x,x);
y>>=1;
}
return ans;
}
Mat init(){
ll i,j;
for(i=1;i<=len;i++)
e.mt[i][i]=1;
memset(a.mt,0,sizeof(a.mt));
for(i=1;i<=len;i++){
if(num_1(i-1)<=k){
ll t1=((i-1)>>1),t2=(((i-1)>>1)|(1<<(m-1)));
a.mt[i][t1+1]=1;
if(num_1(t2)<=k){
a.mt[i][t2+1]=1;
}
}
}
return pow(a,n);
}
int main(){
cin>>n>>m>>k;
len=(1<<m);
ll i,j,l;
ll ans=0;
Mat A=init();
for(l=0;l<len;l++){
if(num_1(l)<=k){
ans+=A.mt[l+1][l+1];
ans%=mod;
}
}
cout<<ans;
return 0;
}