【题解】[BJOI2012]最多的方案
数位 DP。
定理:我们可以将任意一个数拆分成若干个不相邻的斐波那契数之和。
简单证明一下:
我们从小到大枚举 \(f_i\),如果 \(n\ge f_i\) 就将 \(f_i\) 拆分出来。因为 \(f_i\le n <f_{i+1} = f_{i} + f_{i-1}\),所以拆分后一定不会选 \(f_{i-1}\),归纳一下即可证明。
对于这道题我们先将 \(n\) 拆分成若干个斐波那契数,然后写成 \(0/1\) 串的形式,每次我们可以进行操作 \(100\to 011\)。原串我们记作 \(s\)。
那么我们直接从高位到低位 DP,记 \(f_{i,op},op\in\{0,1\}\) 表示考虑到第 \(i\) 位,是否有 \(>i\) 位的位置对第 \(i\) 位产生贡献。
接下来思路就很清晰了,如果 \(op + s_i = 0\),那么 \(f_{i,0} = f_{i - 1, 0}\),如果 \(op + s_i = 1\),则 \(f_{i,1} = f_{i-1,0} + f_{i-2,1}\),最后一种情况 \(op+s_i= 2\),此时 \(f_{i,1} = f_{i-2,1}\)。
时间复杂度 \(\mathcal{O}(\log n)\),看了一圈发现这个 DP 比题解区的更简单,所以写一篇题解。
#define N 150
LL n, f[N], p[N][2]; int m, u[N];
LL calc(int x,int op){
if(~p[x][op])return p[x][op];
if(u[x] && op){
if(x <= 2 || u[x - 1])return p[x][op] = 0;
return p[x][op] = calc(x - 2, 1);
}
if(x == 1)return p[x][op] = 1;
if(!u[x] && !op)return p[x][op] = calc(x - 1, 0);
p[x][op] = calc(x - 1, 0);
if(x > 2 && !u[x - 1])p[x][op] += calc(x - 2, 1);
return p[x][op];
}
int main() {
read(n);
f[0] = f[1] = m = 1;
while(f[m] < n)f[m + 1] = f[m] + f[m - 1], m++;
pr(i, m){
if(n >= f[i])u[i] = 1, n -= f[i];
}
memset(p, ~0, sizeof(p));
printf("%lld\n", calc(m, 0));
return 0;
}