P1357 花园
算法
我们发现题目中\(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;
}