题解 P2704 [NOI2001] 炮兵阵地

题目描述

Link

给定一张 \(n\times m\) 的网格,求这张网格上最多能放多少个炮兵。

网格上有一些地方不能放炮;炮兵的攻击范围是前、后、左、右各 \(2\) 格。

\(1 \leq n \leq 100 ,1 \leq m \leq 10\)

Solution

发现 \(m\) 很小,且第 \(i\) 行的状态之和 \(i-1,i-2\) 行有关,考虑状压 \(dp\)

\(f_{i,j,k}\) 表示前 \(i\) 行中,第 \(i\) 行的状态是 \(j\) ,第 \(i - 1\) 行的状态是 \(k\) 的方案中,最多能放多少个炮兵。

\(vaild(i,j)\) 表示 \(i,j\) 这两个状态能不能放在相邻两行, \(check(i,jj)\) 表示把状态 \(j\) 放到第 \(i\) 行是否合法, \(count(j)\) 表示状态 \(j\) 中放置了几个炮兵(二进制中 \(1\) 的个数)。

如果 \(check(i,j) = false\) 或者 ,\(check(i-1,k)=false\) ,那么 \(f_{i,j,k} = 0\)

考虑怎么计算状态,我们可以枚举第 \(i-2\) 行的状态 \(p\) ,则:

\[f_{i,j,k}=\begin{cases} \max\{f_{i-1,k,p}+count(j)\}&&vaild(j,k) \and vaild(j,p) \and vaild(k,p) \\ 0 && vaild(j,k)=false \end{cases} \]

时间复杂度 \(\mathcal{O}(n2^m2^m2^m)=\mathcal{O}(n8^m)\) ,空间复杂度 \(\mathcal{O}(n4^m)\)

考虑优化:同一行内合法状态很少,所以我们可以先预处理出所有合法状态(最多 \(60\) 个),就可以把时间复杂度降到 \(\mathcal{O}(nc^3)\) ,空间复杂度降到 \(\mathcal{O(nc^2)}\) ,其中 \(c\) 表示合法状态数。

代码如下:

#include <cstdio>
#include <cstring>
#include <cctype>
inline int max(int a ,int b) {return a > b ? a : b;}
inline int lowbit(int x) {return x & (-x);}
inline int count(int x) {
	int ans = 0;
	while (x) ans++ ,x -= lowbit(x);
	return ans;
}
inline bool check(int x) {
	return ((x & (x >> 1)) == 0) && ((x & (x >> 2)) == 0);
}
inline bool check(int x ,int y) {return (x & y) == 0;}
inline bool check(int x ,int y ,int z) {
	return check(x ,y) && check(x ,z) && check(y ,z);
}
const int N = 105 ,M = 15 ,S = 75;
int f[N][S][S] ,n ,m ,s[S] ,bin[S] ,idx ,v[N]; char str[M];
inline bool vaild(int i ,int j) {
	return (v[i] & s[j]) == s[j];
}
signed main() {
	scanf("%d%d" ,&n ,&m);
	for (int i = 1; i <= n; i++) {
		scanf("%s" ,str + 1);
		for (int j = 1; j <= m; j++)
			v[i] = v[i] << 1 | (str[j] == 'P');
	}
	for (int i = 0; i < (1 << m); i++)
		if (check(i)) s[++idx] = i ,bin[idx] = count(i);
	for (int i = 1; i <= idx; i++)
		for (int j = 1; j <= idx; j++)
			if (vaild(1 ,j) && vaild(2 ,i) && check(s[i] ,s[j]))
				f[2][i][j] = bin[i] + bin[j];
    //先预处理出第 2 行的信息,不然会越界造成错误。
    //不用预处理第 1 行是因为这里没有用到。
	for (int i = 3; i <= n; i++)
		for (int j = 1; j <= idx; j++)
			if (vaild(i ,j))
				for (int k = 1; k <= idx; k++)
					if (vaild(i - 1, k) && check(s[j] ,s[k]))
						for (int p = 1; p <= idx; p++)
							if (vaild(i - 2 ,p) && check(s[j] ,s[k] ,s[p]))
								f[i][j][k] = max(f[i][j][k] ,f[i - 1][k][p] + bin[j]);
	int ans = 0;
	for (int i = 1; i <= idx; i++)
		for (int j = 1; j <= idx; j++)
			ans = max(ans ,f[n][i][j]);
	printf("%d\n" ,ans);
	return 0;
}
posted @ 2021-02-22 17:30  recollector  阅读(70)  评论(0编辑  收藏  举报