Skip to content

T01185: 炮兵阵地

状压dp, http://cs101.openjudge.cn/practice/01185/

司令部的将军们打算在NM的网格地图上部署他们的炮兵部队。一个NM的地图由N行M列组成,地图的每一格可能是山地(用"H" 表示),也可能是平原(用"P"表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示: img 如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。 现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。

输入

第一行包含两个由空格分割开的正整数,分别表示N和M; 接下来的N行,每一行含有连续的M个字符('P'或者'H'),中间没有空格。按顺序表示地图中每一行的数据。N <= 100;M <= 10。

输出

仅一行,包含一个整数K,表示最多能摆放的炮兵部队的数量。

样例输入

5 4
PHPP
PPHH
PPPP
PHPP
PHHP

样例输出

6

来源

Noi 01

【陈子良 25物理学院】这题与30191互不侵犯是类似的,只是情况更复杂一些。首先,由于山的存在,不同行的可能状态是不一样的;其次,当前可能的状态与前面两行的状态有关。

我们用函数state(i)生成矩阵第i行的单行可能状态a。它有两个要求:一个是炮兵间距要大于2,即a&(a<<1)==0 and a&(a>>1)==0 and a&(a<<2)==0 and a&(a>>2)==0;另一个是炮兵不能放在山上,我们用s表示矩阵第i行的山的位置,s的二进制中第j位为1表示第i行第j列为山,合法状态要求a&s==0

接下来滚动地推导前n行能放置的最多的炮兵数量。我们记state0为当前行可能状态集合,state1为上一行可能状态集合,state2为上两行可能状态集合。用dp[n][i][j]表示考虑到矩阵的前n行,当前行状态为a=state0[i],上一行状态为b=state1[j]时的最大炮兵数量,其中维度n是滚动的。如果ab冲突,即a&b!=0,直接记为0。如果ab不冲突,遍历上两行可能状态c=state2[k],如果a,b,c不冲突,即a&b==a&c==b&c==0,那么选取其中炮兵数量最多的状态,即max(dp[n-1][j][k]),再加上a中含有的炮兵数num(a)就是dp[n][i][j]的值。

对于状压dp类题,对可能的状态进行提前筛选并存储到state中,这一步是十分必要的,否则会TLE。但这也导致对系统状态的描述变得非常抽象,一定要仔细思考、多加体会。

python
N,M=map(int,input().split())
grid=[]
for _ in range(N):
    grid.append(list(input()))
# 状态a中的炮兵数
def num(a):
    return bin(a).count('1')
# 生成第i行所有合法的单行状态
def state(i):
    l=grid[i][:]
    x=0
    s=0
    while l:
        if l.pop()=='H':
            s+=2**x
        x+=1
    l1=[]
    for a in range(1<<M):
        if a&(a<<1) or a&(a>>1) or a&(a<<2) or a&(a>>2):
            continue
        if not a&s:
            l1.append(a)
    return l1
# N=1情形特判
if N==1:
    state0=state(0)
    print(max([num(a) for a in state0]))
    exit()
# 初始化
state2=state(0) # 上两行状态
state1=state(1) # 上一行状态
dp=[[0]*len(state2) for _ in range(len(state1))]
for i in range(len(state1)):
    for j in range(len(state2)):
        b=state1[i]
        c=state2[j]
        if not b&c:
            dp[i][j]=num(b)+num(c)
# dp的n方向维度是滚动的
for n in range(2,N):
    state0=state(n) # 当前行状态
    dp1=[[0]*len(state1) for _ in range(len(state0))]
    for i in range(len(state0)):
        a=state0[i]
        m=num(a)
        for j in range(len(state1)):
            b=state1[j]
            if a&b:
                continue
            for k in range(len(state2)):
                c=state2[k]
                if not a&c and not b&c:
                    dp1[i][j]=max(dp1[i][j],m+dp[j][k])
    dp=[row[:] for row in dp1]
    state2=state1[:]
    state1=state0[:]
print(max([max(row) for row in dp]))