算法
我们发现题目中(m < 5)很小
所以我们可以使用二进制压缩。
我们用(1)来表示(C) 用(0)表示(P),我们压缩的是当前这个状态的最后(m)位的状态 比如后(m)位是(CCPP)则压缩为((1100)_2)这个数
接下来我们讨论一下状态如何转移
首先好像我对于一个状态的转移和其他题解的方法不大相同。
我对于一个状态向其他状态的转移是这样的:
now_0 = (now << 1) & ((1 << m) - 1);
now_1 = (now << 1) & ((1 << m) - 1) + 1;
这个转移可能是更加符合题目的意思吧。
举个栗子:
就如题目中所给的例子 (m = 2 k = 1)
我们就有如下的转移:
(00 -> 01)
(00 -> 00)
(10 -> 01)
(10 -> 00)
(01 -> 10)
我们可以发现从现在这个状态([i ,i + m])到([i + 1,i + m + 1])的状态转移相当把([i,i + m]) 这个状态的第一个数字挤出去,再在最后一位填上新的数字,我们设(f[i][j])为第(i)位向后(m)位状态为(j)的方案数。
那么我们就知道如果我们枚举([0,m])的状态 则我们的答案是(f[n + 1][j] j)在(2)进制下(1)的个数小于(k)。我们还发现每个(f[i][j])都从(f[i - 1][j])达到,所以我们可以用上滚动数组 这样能够做到(70pts)。
但我们可是冲着(100pts)去的啊 区区(70pts)
我们可以预处理出这个一个矩阵,类似于邻接矩阵,将(i ->j)这个一个关系,当成一条边存入,这个过程可以使用(dfs)。然后我们要做的是转移,就转换成了经典的图上求到达每个点的方案数了,类似于P2886 [USACO07NOV]Cow Relays G这个用矩阵快速幂的题目(建议先做这个题),我们本题也可以使用矩阵快速幂来进行加速。
最后上代码啦
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
#define MOD 1000000007
using namespace std;
ll n,m,k,fin = 0;
bool vis[(1 << 6) + 1][(1 << 6) + 1];
struct map{
ll f[(1 << 6) + 1][(1 << 6) + 1];
map(){
memset(f,0,sizeof(f));
}
}a,ans;
void dfs(ll now,ll last){
if(vis[last][now])
return;
if(now >= (1 << m))
return;
vis[last][now] = 1;
a.f[last][now] = 1;
ll z = now;
now = (now << 1) & ((1 << m) - 1);
if(__builtin_popcount(now) >= k)
dfs(now,z);
else{
dfs(now + 1,z);
dfs(now,z);
}
}
map operator * (const map &x,const map &y){
map z;
for(ll i = 0;i <= (1 << m) - 1;i ++)
for(ll j = 0;j <= (1 << m) - 1;j ++)
for(ll k = 0;k <= (1 << m) - 1;k ++)
z.f[i][j] = (z.f[i][j] + (1ll * x.f[i][k] * y.f[k][j]) % MOD) % MOD;
return z;
}
void quick(ll k){
ans = a;
k -= 1;
while(k){
if(k & 1) ans = ans * a;
a = a * a;
k >>= 1;
}
}
int main(){
scanf("%lld%lld%lld",&n,&m,&k);
dfs(0,0);
quick(n);
for (int i = 0; i < (1 << m) - 1; ++i) {
fin = (fin + ans.f[i][i]) % MOD;
}
cout<<fin;
}