题目链接
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;
}