花园
也是一道不错的状压练习题,梳理一遍思路。
首先,很明显,我们并不能够用一个简洁组合式子表示原问题答案。我们想到了递推计数。
不难想到,由于这个是一个环,我们拆环,将其转化成序列上的问题。同时,我们枚举该序列上最后(m)个数,分别对枚举的这些情况一一统计答案。
具体来讲,我们可以令(dp(i,S))为前(i)个数后(m)位状态为(S)的方案个数:
[dp(i,S)=dp(i-1,S>>1)+dp(i-1,(S>>1)|(1<<m-1))
]
这里,我们只考虑(S)合法的情况。
注意到该方程前一维仅涉及两个位置的状态值,因此滚动数组优化空间:
const int S = 1 << 6, mod = 1000000000 + 7;
int dp[2][S], ans = 0;
for(int S0 = 0; S0 < 1 << m; ++ S0)
{
if(!valid[S0]) continue;//预处理状态
memset(dp, 0, sizeof dp);
dp[0][S0] = 1;
for(int i = 1; i <= n; ++ i)
{
for(int s = 0; s < 1 << m; ++ s)
{
if(!valid[s]) continue;
bool op = i & 1;
dp[op][s] = (dp[1 - op][s >> 1] + dp[i - op][(s >> 1) | (1 << m - 1)]) % mod;
}
}
ans = (ans + dp[n][S0]) % mod;
}
printf("%d
", ans);
接着发现(n)太大了,需要矩阵加速递推。
我们可以考虑,对于一个状态矩阵:
(egin{bmatrix} dp(0) & dp(1) & … & dp(2^{m-1}-2) & dp(2^{m-1}-1)end{bmatrix})
可以构造一个转移矩阵使其能够递推。
考虑到:对于转移矩阵而言,新的状态值仅依赖于不超过两个状态,因此我们可以对于每一个状态计算出来。
最后发现,我们由于枚举些许状态拆环,我们可以直接将初始的矩阵构造成这个样子,答案为每个合法状态(mat(k,k))。
(egin{bmatrix} dp(0) & … & … \… & dp(1) & … &\…\…& …end{bmatrix})
…
typedef long long LL;
const int N = 1e5 + 5, S = 40, mod = 1e9 + 7;
bool valid[S] = {};
LL n;
int m, k, ans = 0, dp[2][S], mat[S][S] = {};
int calc(int x)
{
int res = 0;
while(x)
{
++ res;
x -= lowbit(x);
}
return res;
}
void mul(int (*c)[S], int (*a)[S], int (*b)[S])
{
static int res[S][S];
CLR(res, 0);
for(int i = 0; i < 1 << m; ++ i)
for(int j = 0; j < 1 << m; ++ j)
for(int p = 0; p < 1 << m; ++ p)
res[i][j] = (res[i][j] + 1ll * a[i][p] * b[p][j] % mod) % mod;
memcpy(c, res, sizeof res);
}
void power(int (*c)[S], int (*a)[S], LL b)
{
static int res[S][S];
CLR(res, 0);
for(int i = 0; i < 1 << m; ++ i) res[i][i] = 1;
while(b)
{
if(b & 1) mul(res, res, a);
b >>= 1;
mul(a, a, a);
}
memcpy(c, res, sizeof res);
return;
}
int main()
{
scanf("%lld %d %d", &n, &m, &k);
for(int s = 0; s < 1 << m; ++ s) valid[s] = calc(s) <= k;
for(int i = 0; i < 1 << m; ++ i)
{
int state = (i >> 1) | (1 << m - 1);
mat[i >> 1][i] = 1, mat[state][i] = valid[state];
}
power(mat, mat, n);
for(int i = 0; i < 1 << m; ++ i) if(valid[i]) ans = (ans + mat[i][i]) % mod;
printf("%d
", ans);
return 0;
}
总结:
- 对付环状问题的技巧一般有两个:分类讨论和拆环;
- 如果递推的时候发现迭代过多,应当考虑矩阵加速处理该问题。