算法分析
注意:题目中还有'H'
位置不能放大炮,需要特殊标记一下,w[i]
记录的是第i
行能不能放大炮的状态,w[i]
的二进制表示中,某位是1
表示该位置不能放大炮,0
表示该位置可以放大炮,当枚举到当前状态state
时,若state & w[i] == 0
表示合法
时间复杂度
上限复杂度是$O(n ∗ s^2 ∗ k)$,n
表示行数,s
表示合法状态数量,k
表示合法状态的转移数量
参考文献
算法提高课
Java 代码
import java.util.ArrayList;
import java.util.Scanner;
public class Main {
static int N = 110,M = 1 << 10;
static int[] w = new int[N];
static int n , m;
static ArrayList<Integer> state = new ArrayList<Integer>();
static int[] cnt = new int[M];
static int[][][] f = new int[2][M][M];
static boolean check(int state)
{
for(int i = 0;i < m;i ++)
{
if((state >> i & 1) == 1 && ((state >> i + 1 & 1) == 1 || (state >> i + 2 & 1) == 1)) return false ;
}
return true;
}
static int count(int s)
{
int res = 0;
for(int i = 0;i < m;i ++)
if((s >> i & 1) == 1)
res ++;
return res;
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
n = scan.nextInt();
m = scan.nextInt();
for(int i = 1;i <= n;i ++)
{
char[] g = scan.next().toCharArray();
int t = 0;
//不可填表示1,可填表示0
for(int j = 0;j < m;j ++)
{
if(g[j] == 'H') t += (1 << j);
}
w[i] = t;
}
//预处理出相邻不为1的状态
for(int i = 0; i < 1 << m;i ++)
{
if(check(i))
{
state.add(i);
cnt[i] = count(i);
}
}
for(int i = 1;i <= n;i ++)
for(int j = 0;j < state.size();j ++)//当前行
for(int k = 0;k < state.size();k ++)//倒数第二行
for(int u = 0;u < state.size();u ++)
{
int a = state.get(j);
int b = state.get(k);
int c = state.get(u);
if((a & b) != 0 || (a & c) != 0 || (b & c) != 0) continue;
if((w[i] & a) != 0 || (w[i - 1] & b) != 0) continue;
f[i & 1][j][k] = Math.max(f[i & 1][j][k], f[i - 1 & 1][k][u] + cnt[a]);
}
int res = 0;
for(int i = 0;i < state.size();i ++)
for(int j = 0;j < state.size();j ++)
res = Math.max(res,f[n & 1][i][j]);
System.out.println(res);
}
}
为啥要把i-1行放在dp循环最外面,然后再是第i和第i-2行,按理来说不应该第i行在最外面吗,这样的话可以保证枚举到第i行时第i-1行已被算过
同问
有一个地方没有看懂,枚举的时候分别枚举当前行,上一行,上上行三个状态,为什么在检查和w[]是否有冲突的时候上上行没有检查,而只检查当前行和上一行
有人问过,就搬过来了hh
这行实际上是检查当前行摆放是否合法。所以每行只需被检测一次即可。
检查g[i] & b时,g[i - 1] & a被上次循环,g[i - 2] : 0) & c被上上次循环检测过了。若非法相应的 f 必为0。
加多这个
if(i >= 2 && (w[i - 2] & c) != 0) continue;
也可以的感谢解惑,我本来也是增加了 i >= 2 的判断,看了你的回答之后,我只保留了对当前行的检查 也是可以的
可是如果是非法的 f=0加上cnt[]后还是可能会更新当前值的吧
想请问一下为什么这个题和玉米田的题目都是只跟下标i-1的状态有关,但玉米田的那个题不能用滚动数组呢
也是可以用的,因为第
i
行只会用到第i-1
行的数据这是后面直接用值作为位置写的,和题解的有小小不同,原理是一样的
炮兵阵列:
玉米田:
谢谢大佬,我明白了,玉米田这道题在开始新的行的时候必须得把当前行的值都清空,不然会受到上一行的影响,也就是f[i & 1][state[a]] = 0;必须得有。
是的
f[i & 1][j][k] = Math.max(f[i & 1][j][k], f[i - 1 & 1][k][u] + cnt[a]);
这个地方,会不会存在可能,f[i & 1][j][k]存储的上一次最大值比现在所有的f[i - 1 & 1][k][u] + cnt[a]都要大,举个例子,i=5的时候,记录了一个f[5 & 1][j][k],到i=7的时候,会不会所有的f[6 & 1][k][u] + cnt[a]都比f[5 & 1][j][k]小,这样第七行存储的值其实是第五行的状态呀
会,第5层的集合的最大值会给第6层,第6层集合的最大值会给第7层