题目链接:传送门
题目:
题目描述 小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。
思路:
乍一看是一个环形dp:
对于给定的长度为M的状态,其后面长度为M-1的部分会影响下一个状态,记一个cnt1表示已经放了C型花圃的数量,那么根据当前的cnt1是否小于K,可以决定下个状态的转移。
状态:
f[i][j]:以第i个位置为终点的长度为M的部分,状态为j的方案数。(j是用2进制状压的一个长度为M的状态)
状态转移方程:
f[i][j] += f[i-1][j>>1];
if (count1(j) == K && !(j&1))
f[i][j] += f[i-1][(j>>1)|(1<<M)];
。。。。。。
但是N的上限高达1e15,所以常规的方法没发跑,考虑用矩阵加速。
那就要找状态矩阵和转移矩阵了。
状态矩阵:
Fn = [f[n][0], f[n][1], ... , f[n][(1<<m)-1];
转移矩阵可以通过上面的状态转移方程,用dfs预处理出来,接下来就可以用快速幂加速了。
因为跑N次之后和不跑的时候状态相同(环形),所以直接求转移矩阵A的N次AN的对角线上的和即可。
代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int MOD = 1e9 + 7; ll N, M, K; int bin[6]; ll F[32][32], A[32][32]; void addTran(int sta, int cnt1) { int pre = sta >> 1; A[pre][sta] = 1; if (cnt1 == K && !(sta&1)) return; A[pre|bin[M-1]][sta] = 1; } void dfs(int dep, int sta, int cnt1) { if (dep == M) { addTran(sta, cnt1); return; } dfs(dep+1, sta, cnt1);//dep+1位为0 if (cnt1 < K && dep+1 < M) dfs(dep+1, sta|bin[dep+1], cnt1+1);//dep+1位为1 } void mul(ll f[32][32], ll a[32][32]) { ll c[32][32]; memset(c, 0, sizeof c); for (int i = 0; i < 32; i++) for (int j = 0; j < 32; j++) for (int k = 0; k < 32; k++) c[i][j] = (c[i][j] + f[i][k] * a[k][j]) % MOD; memcpy(f, c, sizeof c); } int main() { bin[0] = 1; for (int i = 1; i <= 5; i++) bin[i] = bin[i-1] << 1; cin >> N >> M >> K; memset(A, 0, sizeof A); memset(F, 0, sizeof F);//所有长度为M的状态 for (int i = 0; i < bin[M]; i++) F[i][i] = 1; dfs(0, 0, 0);//初始化转移矩阵 dfs(0, 1, 1); for (; N; N >>= 1) { if (N&1) mul(F, A); mul(A, A); } ll ans = 0; for (int i = 0; i < bin[M]; i++) ans = (ans + F[i][i]) % MOD; cout << ans << endl; return 0; } /* 6 2 1 */