花园

花园


也是一道不错的状压练习题,梳理一遍思路


首先,很明显,我们并不能够用一个简洁组合式子表示原问题答案。我们想到了递推计数。

不难想到,由于这个是一个环,我们拆环,将其转化成序列上的问题。同时,我们枚举该序列上最后\(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\n", ans);

接着发现\(n\)太大了,需要矩阵加速递推。

我们可以考虑,对于一个状态矩阵:

\(\begin{bmatrix} dp(0) & dp(1) & … & dp(2^{m-1}-2) & dp(2^{m-1}-1)\end{bmatrix}\)

可以构造一个转移矩阵使其能够递推。

考虑到:对于转移矩阵而言,新的状态值仅依赖于不超过两个状态,因此我们可以对于每一个状态计算出来。

最后发现,我们由于枚举些许状态拆环,我们可以直接将初始的矩阵构造成这个样子,答案为每个合法状态\(mat(k,k)\)

\(\begin{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\n", ans);
	return 0;
} 

总结:

  1. 对付环状问题的技巧一般有两个:分类讨论和拆环;
  2. 如果递推的时候发现迭代过多,应当考虑矩阵加速处理该问题。
posted @ 2021-02-15 19:14  大秦帝国  阅读(32)  评论(0编辑  收藏  举报