[CF111C Petya] and Spiders

本文探讨了如何使用状压动态规划解决蜘蛛在棋盘上行走的问题,通过转化问题为棋盘覆盖,定义状态并进行状态转移,最终求得最小点数覆盖整个棋盘的方法。

题目

洛谷

思路

首先, m i n ( n , m ) < = 6 min(n,m)<=6 min(n,m)<=6 是很容易判断出来的。

于是我想着:这么小的数据范围,能够直接分类讨论吗?但由于是状压dp板块的题,也没有从这方面细想,题解中似乎有分类讨论的做法。

想状压dp的做法。由于之前状压dp做得太少,实在是没什么想法。看了题解梳理一下。

由于蜘蛛可以往上下左右四个方向走,以及原地待着,我们将题目转化为:在棋盘上选一些点,点可以覆盖上下左右即自身,求最少多少点可以将棋盘完全覆盖。

定义 d p [ i ] [ k 1 ] [ k 2 ] dp[i][k1][k2] dp[i][k1][k2] 为第 i i i 行状态为 k 1 k1 k1 i − 1 i-1 i1 行为 k 2 k2 k2 的最小点数。因为只有上中下三行对第 i i i 行会有影响,所以这样考虑状态不会有缺漏。

状态转移为 d p [ i ] [ k 1 ] [ k 2 ] = m i n ( d p [ i − 1 ] [ k 2 ] [ k 3 ] + c o u n t ( k 1 ) ) dp[i][k1][k2]=min(dp[i-1][k2][k3]+count(k1)) dp[i][k1][k2]=min(dp[i1][k2][k3]+count(k1))

Code

LL n, m, dp[MAXM][MAXN][MAXN];
// dp[i][k1][k2] : 
// 第 i 行状态为 k1 ,第 i-1 行状态为 k2 的最小点数
inline LL Count(LL Zt)
{
	LL Res = 0;
//	for (Int i = 0; i < m; ++ i)
//		Res += (Zt & (1 << i));
	for (Int i = 0; i < m; ++ i)
		if (Zt & (1 << i))
			Res ++;
	return Res;
}
int main()
{
	read( n ); read( m );
	if (n < m)
		Swap(n, m);
	LL Ans = INF, Top = (1 << m) - 1;
	memset(dp, 0x3f, sizeof dp);
	for (Int i = 0; i <= Top; ++ i)
	{
		dp[1][i][0] = Count( i );
		// printf("%d %lld\n", i, dp[1][i][0]);
		if (n == 1)
		{
			bool Flag = 0;
			for (Int j = 0; j < m; ++ j)
				if (! (i & Nw( j ) || (j > 0 && i & Nw(j - 1)) || (j < m - 1 && i & Nw(j + 1))))
				{
					Flag = 1;
					continue;
				}
			if (! Flag)
				Ans = Min(Ans, dp[1][i][0]);
		}
	}
	for (Int i = 2; i <= n; ++ i) // 行数 
	{
		for (Int k3 = 0; k3 <= Top; ++ k3) // 上上行 
			for (Int k2 = 0; k2 <= Top; ++ k2) // 上一行,将上一行当做中间 
				for (Int k1 = 0; k1 <= Top; ++ k1) // 当前行 
				{
					bool Flag = 0;
					for (Int j = 0; j < m; ++ j)
						if (! (k1 & Nw( j ) || k2 & Nw( j ) || k3 & Nw( j ) || (j > 0 && k2 & Nw(j - 1)) || (j < m - 1 && k2 & Nw(j + 1))))
						{
							Flag = 1;
							break;
						}
					if ( Flag )
						continue;
					dp[i][k1][k2] = Min(dp[i][k1][k2], dp[i - 1][k2][k3] + Count( k1 ));
					if (i == n)
					{
						Flag = 0;
						for (Int j = 0; j < m; ++ j)
							if (! (k1 & Nw( j ) || k2 & Nw( j ) || (j > 0 && k1 & Nw(j - 1)) || (j < m - 1 && k1 & Nw(j + 1))))
							{
								Flag = 1;
								break;
							}
						if ( Flag )
							continue;
						Ans = Min(Ans, dp[i][k1][k2]);
					}
				}
	}
	printf("%lld", n * m - Ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值