P1357 花园(状压dp + 矩阵快速幂)

传送门

此题是一个很好的题目,可以加深对矩阵快速幂的理解。若对基本的矩阵快速幂不太理解,需要先了解一下简单的比较裸一点的题目后再看本题目,更有利于理解。可以先学习一下 HDU_2842。本人写过这道题目的题解,但是并不详细,仅供参考我的题解HUD2842

Solution

参考题解1
参考题解2

  1. 假设:长度m的花圃所有的状态为 [ 0 , S ] , S = 2 m − 1 [0, S], S = 2^m - 1 [0,S],S=2m1。 状态1表示C形花圃,0表示P形花圃
  2. 我们假设花园为链状的,则:状态转移方程为
ll t1=j>>1,t2=(j>>1)|(1<<(m-1));
if(num_1(t2)<=k)//1的个数小于等于k
    f[i][j]=f[i-1][t1]+f[i-1][t2];
else
    f[i][j]=f[i-1][t1];
  1. 由于是环形的,我只需要由某种长度为m的合法状态的花园进行状态转移n次,然后保证最后一次的状态与初始状态相同即可。
  2. 假设状态转移矩阵 X 中某个点a[i, j] = 1,表示状态 i 可由状态 j 转移 过来。
    现在假设 [ 0 , S ] [0, S] [0,S]中每一个合法状态的矩阵为 A
    ( 0 0 0 i 0 . . . 0 ) \begin{pmatrix} 0\\0\\0\\i\\0\\...\\0 \end{pmatrix} 000i0...0
    此矩阵共S + 1 行, i ∈ [ 0 , S ] i\in[0, S] i[0,S],表示某一种长度为m的状态为i的花园。
    F = X n × A F = X ^ n \times A F=Xn×A。显然,F 为 1 × ( S + 1 ) 1 \times (S + 1) 1×(S+1) 的矩阵。
    则此种情况下的方案数为 F [ i ] F[i] F[i]
    由此,我只需要分别求出所有状态的F矩阵然后求和即可。

但是,有一种更为简便的方法:
5. P = X n P = X ^ n P=Xn , a n s = ∑ i = 0 S P [ i ] [ i ] , i 为 合 法 状 态 ans = \sum^S_{i = 0} P[i][i], i为合法状态 ans=i=0SP[i][i]i
6. 仔细思考后发现,其实就是把所有的合法方案放在一个 ( S + 1 ) × ( S + 1 ) (S + 1) \times (S + 1) (S+1)×(S+1)的矩阵M中,第i 行为 状态为 i 的 1 × ( S + 1 ) 1 \times (S + 1) 1×(S+1)的矩阵
(也可看做单位矩阵,只不过最后求和只加上合法的方案即可, 即 P = X n × E P = X^n \times E P=Xn×E。)。
8. 然后对于每行中的状态 i 其结果 自然是 第 i 列的末状态(由于是原型花园嘛,得保证初始状态和最终的状态相同)。所以最终的答案就是 P 矩阵的斜对角线了。

Code

#define mt(a,b) memset(a, b, sizeof a)
#define ll long long int
const ll mod = 1000000007;
using namespace std;
const int N = (1 << 5) + 5;
ll n;
int m, k, S;
int ok[N];//合法状态

//矩阵
struct matrix
{
	ll a[N][N];

	matrix() { mt(a, 0); }
	matrix operator * (const matrix x) const
	{
		matrix ans;
		for (int i = 0; i <= S; i++)
			for (int j = 0; j <= S; j++)
				for (int k = 0; k <= S; k++)
					ans.a[i][j] = (ans.a[i][j] + x.a[i][k] * a[k][j]) % mod;//?
		return ans;
	}
};

ll power(ll n)
{
	matrix e, dp;
	for (int i = 0; i <= S; i++)//单位矩阵初始化
		e.a[i][i] = 1;

	for (int i = 0; i <= S; i++)// 求转移矩阵
		if (ok[i])
		{
			int t1 = i >> 1;
			int t2 = t1 | 1 << (m - 1);
			dp.a[i][t1] = 1;
			if (ok[t2])
				dp.a[i][t2] = 1;
		}

	for (; n; n >>= 1)
	{
		if (n & 1) e = e * dp;
		dp = dp * dp;
	}

	ll ans = 0;
	for (int i = 0; i <= S; i++)
		if (ok[i])
			ans = (ans + e.a[i][i]) % mod;

	return ans;
}

int main()
{
	cin >> n >> m >> k;
	S = (1 << m) - 1;//状态

	for (int i = 0; i <= S; i++)
	{
		int cnt = 0, x = i;
		while (x) x -= x & -x, cnt++;
		if (cnt <= k) ok[i] = 1;
	}

	cout << power(n) << endl;


	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

to cling

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值