状态压缩——炮兵阵地
(第一次做状压,难死我了QAQ)
题目描述
司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队。一个N*M的地图由N行M列组成,地图的每一格可能是山地(用“H” 表示),也可能是平原(用“P”表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:

如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。 现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
输入输出格式
输入格式:
第一行包含两个由空格分割开的正整数,分别表示N和M;
接下来的N行,每一行含有连续的M个字符(‘P’或者‘H’),中间没有空格。按顺序表示地图中每一行的数据。N≤100;M≤10。
输出格式:
仅一行,包含一个整数K,表示最多能摆放的炮兵部队的数量。
输入输出样例
5 4 PHPP PPHH PPPP PHPP PHHP
这道题在我没有学过状压dp之前,似乎只有搜索这一条路可以走,这么大的规模(搜索肯定是不可能的了)
那么状态压缩动态规划就出现了,状态压缩动态规划又称为集合动态规划。
顾名思义,这种动态规划是将集合信息为状态记录的特殊动态规划,主要有传统的集合动态规划和基于连通性状态压缩的动态规划。
一般的动态规划根据整体,从中提取中几个关键信息,同时,划分阶段将问题具备无后效性和最优子结构性质,然后根据已知的信息对于各个阶段进行决策,
但是对于状态压缩动态规划而言,一般的简单状态描述已经无法满足无后效性原则了,所以我们需要对于多个元素的状态进行压缩,这便是状态压缩动态规划的由来
(一看就知道不是我说的o(* ̄︶ ̄*)o)
这道题呢?
发现一个炮兵的防止最多影响到下面的两层,所以我们对于此题的动态规划
用状态f[i][j][k]表示当前放到了哪一层,这层的状态为k,上层的状态是j(j,k均想象为2进制数,对于二进制上第i-1位的1表示在第i个位置上有没有放炮兵,我们用十进制存储)
那么转移就为
f[i][j][k]:=max(f[i-1][w][j])+g[k](g[k]表示k中的炮兵数量)(w枚举合法状态同时判断这三行的放置是否合法)
那么我们怎么枚举生成状态呢
for i:=0 to (1<<m)-1 do
if ((i and (i<<1)=0) and (i and (i<<2)=0)) then
begin
inc(num);//状态数加+1
sta[num]:=i;
g[num]:=bits(i);//计算i中1的数量
if (i and map[1]=0) then f[1][0][num]:=g[num];//手动dp第一层,判断和第一层的地形是否合法
end;
然后,我们需要对于当前层的状态枚举上两层的状态来判断是否是合法的
for i:=3 to n do//我好想莫名其妙的初始化了第2层
for j:=1 to num do//枚举所以状态
if sta[j] and map[i]=0 then//和第i层的地形是否合法
for k:=1 to num do//枚举上一层的状态
if sta[k] and sta[j]=0 then//上一层和这一层是否合法
for l:=1 to num do//枚举上上层状态
if ((sta[l] and sta[k]=0) and (sta[l] and sta[j]=0)) then//上上层和上层和当前层是否有冲突
f[i][k][j]:=max(f[i][k][j],f[i-1][l][k]+g[j]);//转移
所以,代码如下
var
n,m,i,j,k,num,l,ans:longint;
map,g,sta:array[0..10000] of longint;
f:array[0..100,0..100,0..100] of longint;
x:char;
function max(a,b:longint):longint;
begin
if a>b then exit(a)
else exit(b);
end;
function bits(d:longint):longint;
var sum:longint;
begin
sum:=0;
while (d>0) do
begin
inc(sum);
d:=d-((d) and (-d));
end;
exit(sum);
end;
begin
readln(n,m);
for i:=1 to n do
begin
for j:=0 to m-1 do
begin
read(x);
if (x='H') then map[i]:=map[i]+(1<<j);
end;
readln;
end;
for i:=0 to (1<<m)-1 do
if ((i and (i<<1)=0) and (i and (i<<2)=0)) then
begin
inc(num);
sta[num]:=i;
g[num]:=bits(i);
if (i and map[1]=0) then f[1][0][num]:=g[num];
end;
for i:=1 to num do
for j:=1 to num do
if ((sta[i] and sta[j]=0) and (map[2] and sta[j]=0)) then
f[2][i][j]:=max(f[2][i][j],f[1][0][i]+g[j]);
for i:=3 to n do
for j:=1 to num do
if sta[j] and map[i]=0 then
for k:=1 to num do
if sta[k] and sta[j]=0 then
for l:=1 to num do
if ((sta[l] and sta[k]=0) and (sta[l] and sta[j]=0)) then
f[i][k][j]:=max(f[i][k][j],f[i-1][l][k]+g[j]);
for i:=1 to num do
for j:=1 to num do
ans:=max(ans,f[n][i][j]);
writeln(ans);
end.
其实,这类方法我们需要跟加精确的计算时间复杂度
看上去枚举三层的状态需要8^n,但是我们发现一行上合法的状态都不会超过60个,那么8^n次直接就变成了60^3,emmm然后就过了
其实我们枚举了很多不必要的状态,因为发现向上层枚举我们很难优化,那不如就向下枚举,向下枚举我们就会发现下面的放置一定是上面0状态的子集,
那么时间复杂度就会接近我们程序的时间复杂度(似乎状压dp的时间复杂度比较玄学)
那么这道题就讲到这里了
浙公网安备 33010602011771号