Luogu 1357:花园
题目传送门
PS:刷刷水题,娱乐身心
【问题描述】
有n个花圃,形成一个环。每个花圃可以放两种花(A和B)。规定相邻m个花圃里的B花数量不超过k。
求方案总数(%1e9+7)
好像旋转后相同也记作不同。。。(应该是固定视角的)
2<=n<=10^15
2<=m<=5
1<=k
【解题思路】
m<=5,2^5=32。
显然的状压DP。
只有选B有影响。
m个之中的第i个花圃为B,记作2^(i-1)
f[i][(S>>1)+当前第i个选A或选B]+=f[i−1][S];
f
[
i
]
[
(
S
>>
1
)
+
当
前
第
i
个
选
A
或
选
B
]
+
=
f
[
i
−
1
]
[
S
]
;
—————————————————————
发现n太大。
需要用到矩阵乘法加速。
1 2 3 11 12 13
* 21 22 23 =1*11+2*21+3*31 1*12+2*22+3*32 3*13+2*23+3*33
31 32 33
也就是说,f[i][j]的转移规则在于矩阵G的第j列。
f[i][j]=∑nx=1f[i−1][x]∗G[x][j]
f
[
i
]
[
j
]
=
∑
x
=
1
n
f
[
i
−
1
]
[
x
]
∗
G
[
x
]
[
j
]
把所有的转移x->y,设为G[x][y]=1
—————————————————————
那初始状态,可以枚举前m个花圃的状态为S。因为是一个环(破环为链),那么末尾n+1~n+m-1也必定状态为S。
即状态S,经过n次转移,最后状态也是S。
【代码】
#include<bits/stdc++.h>
#define RE register
#define imax(a,b) ((a>b)?(a):(b))
#define imin(a,b) ((a<b)?(a):(b))
using namespace std;
typedef long long ll;
const int N=40;
const ll mods=1000000007;
ll n,m,k,ans;
int wn;
ll A[N][N],B[N][N],C[N][N],S[N],Q[N];
bool ok[N];
void read(ll &x)
{
x=0; RE char ch=getchar(); RE ll f=1;
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
for(; isdigit(ch);ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
x*=f;
}
void pre()
{
for(RE int i=0;i<wn;i++)
{
RE int sk=0;
for(RE int j=0;j<m;j++)
if((1<<j)&i) sk++;
if(sk>k) continue;
ok[i]=1;
RE int yu=i;
if(1&i) sk--;
if(sk<k) { yu=(i>>1)+(1<<(m-1)); A[yu][i]=1; }
yu=(i>>1); A[yu][i]=1;
}
}
void mul()
{
memset(C,0,sizeof(C));
for(RE int i=0;i<wn;i++)
for(RE int j=0;j<wn;j++)
for(RE int g=0;g<wn;g++) (C[i][j]+=B[i][g]*B[g][j]%mods)%=mods;
memcpy(B,C,sizeof(C));
}
void kmul()
{
memset(Q,0,sizeof(Q));
for(RE int i=0;i<wn;i++)
for(RE int j=0;j<wn;j++) (Q[j]+=S[i]*B[i][j]%mods)%=mods;
memcpy(S,Q,sizeof(Q));
}
ll find(ll n,int s)
{
memset(S,0,sizeof(S));
S[s]=1; memcpy(B,A,sizeof(A));
for(;n;n>>=1,mul())
if(n&1) kmul();
return S[s];
}
int main()
{
read(n); read(m); read(k);
wn=(1<<m);
pre(); ans=0;
for(RE int i=0;i<wn;i++) if(ok[i]) (ans+=find(n,i))%=mods;
printf("%lld\n",ans);
return 0;
}