题目大意
小L有一座环形花园,沿花园的顺时针方向,他把各个花圃编号为1~N(2<=N<=10^15)。他的环形花园每天都会换一个新花样,但他的花园都不外乎一个规则,任意相邻M(2<=M<=5,M<=N)个花圃中有不超过K(1<=K<M)个C形的花圃,其余花圃均为P形的花圃。请帮小L求出符合规则的花园种数Mod 1000000007。
题解
状态的设计
我们先不考虑环。我对状态的设计是f(i, j, k),i是以第i位起始,j是区间[i, i+m]中最后一个C的位置-i的值,k是[i, i+m]中C的数量,f是排列的种数。后来我认为j也不需要,f(i, k)就行了。但是此方法一个k无法表示出具体的排布状态,这是错误的。
我们看到M<=5,很容易想到用状态压缩来表示具体的状态。所以我们设计出f(i, S),i是位置,S是[i, i+M]中C的排布状态,f是排列的个数。递推式f(i + 1, S >> 1) += f(i, S), f(i + 1, (S >> 1) | (1 << M - 1) += f(i, S),其中涉及到的所有集合内元素个数都不超过K。
环的处理
我当时想到将长度为n的序列尾部再加一个长度为m的序列,从左往右递推,最后输出的结果便是sum{f(n, S)},S满足元素个数<=K,但考虑到这样会导致结尾m个花的排列状态和开头m个花的排列状态不一致而导致错误。于是我就卡住了。
正确的解决办法是枚举开头[1, m]的花的状态S,每次将其固定住,这样DP推导出的DP(n, S)都是由[m + 1, n + m]处花合法的排列得出的了。
矩阵快速幂优化
我们看到对于每一个状态S0,无论i是多少,可以使f(i, S0)去+=的出处f(i+1, S0')都是固定的。所以我们建立一个矩阵A,A(S1, S2)=[对任意i, f(i + 1, S2)需要+=f(i, S1)]。[]内判断为真即1,否则为0。这样,对于每一个开头[1, m]的花的状态S0,建立一个向量B,其中只有S0那一位的项为1(排列方式由S0知道肯定为1),最终的结果便是B*A^N。最后求和即可。
#include <cstdio> #include <cstring> #include <algorithm> #include <cassert> using namespace std; #define ll long long const int MAX_M = 7; const ll P = 1000000007; ll N, M, K; struct Matrix { private: int TotRow, TotCol; public: ll A[1 << MAX_M][1 << MAX_M]; void Clear() { memset(A, 0, sizeof(A)); } Matrix(int totRow, int totCol) :TotRow(totRow), TotCol(totCol) { Clear(); } Matrix operator * (const Matrix& a) const { assert(TotCol == a.TotRow); Matrix ans(TotRow, a.TotCol); for (int row = 0; row < TotRow; row++) for (int col = 0; col < a.TotCol; col++) for (int k = 0; k < TotCol; k++) ans.A[row][col] = (ans.A[row][col] + (A[row][k] * a.A[k][col] % P)) % P; return ans; } Matrix Power(ll n) { Matrix ans(TotRow, TotCol); for (int i = 0; i < TotRow; i++) ans.A[i][i] = 1; Matrix a = *this; while (n) { if (n & 1) ans = ans * a; a = a * a; n >>= 1; } return ans; } }; int main() { scanf("%lld%lld%lld", &N, &M, &K); static bool Exist[1 << MAX_M]; for (int S = 0; S < (1 << M); S++) { int cnt = 0; for (int i = 0; i < M; i++) cnt += (S & (1 << i)) > 0; Exist[S] = cnt <= K; } static Matrix g(1 << M, 1 << M); for (int S = 0; S < (1 << M); S++) { if (Exist[S]) { g.A[S][S >> 1] = 1; if (Exist[(S >> 1) | (1 << M - 1)]) g.A[S][(S >> 1) | (1 << M - 1)] = 1; } } static Matrix powed = g.Power(N), start(1, 1 << M); ll ans = 0; for (int S = 0; S < (1 << M); S++) { if (Exist[S]) { start.Clear(); start.A[0][S] = 1; ans = (ans + (start * powed).A[0][S]) % P; } } printf("%lld ", ans); }