P2704炮兵阵地之状压dp

本文介绍了一种算法,用于在给定的N×M网格地图上部署炮兵部队,避免炮兵间的攻击冲突,计算在不误伤前提下最多能摆放的炮兵数量。算法通过三维数组和状态转移方程求解,考虑地形和炮兵攻击范围。
摘要由CSDN通过智能技术生成

[NOI2001] 炮兵阵地

题目描述

司令部的将军们打算在 N × M N\times M N×M 的网格地图上部署他们的炮兵部队。

一个 N × M N\times M N×M 的地图由 N N N M M M 列组成,地图的每一格可能是山地(用 H \texttt{H} H 表示),也可能是平原(用 P \texttt{P} P 表示),如下图。

在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:

如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。

图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。

现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。

输入格式

第一行包含两个由空格分割开的正整数,分别表示 N N N M M M

接下来的 N N N 行,每一行含有连续的 M M M 个字符,按顺序表示地图中每一行的数据。

输出格式

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

样例 #1

样例输入 #1

5 4
PHPP
PPHH
PPPP
PHPP
PHHP

样例输出 #1

6

提示

对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 100 1 \leq N\le 100 1N100 1 ≤ M ≤ 10 1 \leq M\le 10 1M10,保证字符仅包含 PH

—————————————————手动分割线——————————————

题解

状态设定

鉴于炮兵阵地每列间会互相影响,因此需要定义一个三维数组分别记录所在行数,本行状态和上行状态。设定dp[i][j][k]表示考虑前i行在第i行摆放方式为j,第i-1行摆放方式为k时的最大炮兵数量。

并不愉快的状态转移

定义一个数组sl[i]表示摆放方式为i时的炮兵数量(这一步在数据的预处理中完成),易得状态转移方程

  • dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][p]+sl[j])

关于地形

同样的,我们可以使用二进制存储地形,再使用与运算就可以高效地排除与地形冲突的摆放方式

特别注意

  • 当列与列间冲突时,dp[i][j][k]不应为0,可使用-1表示冲突(全部初始化成-1完美解决)
  • 注意地形的赋值问题,若平原(P)为0则&摆放方式后数值为0表示摆放方式可行,若平原为1则&摆放方式后为地形的二进制数(摆放方式为地形的子集)表示摆放方式可行

代码区

#include<bits/stdc++.h>
using namespace std;
int n,m,cnt;
int dp[110][70][70],sl[70],kx[1500],mp[110];
void inti()//预处理 
{
	for(int i=0;i<=(1<<m)-1;i++)
	{
		int j=i<<1,k=i<<2;
		if((i&j)==0&&(i&k)==0) kx[cnt]=i,sl[cnt++]=__builtin_popcount(i);//防止同行冲突 
	}
	cnt--;
}
int dx(string s)//处理地形 
{
	int ans=0;
	for(int i=0;i<s.size();i++)
	{
		ans=ans<<1;
		if(s[i]=='H') ans+=1;
	}
	return ans;
}
void out(int h)//高效的查错(迫真) 
{
	for(int i=0;i<=cnt;i++)
	{
		for(int j=0;j<=cnt;j++)
			cout<<dp[h][i][j]<<" ";
		cout<<endl;
	}
	cout<<"--------------------";
}
int main()
{
	cin>>n>>m;
	inti();
	memset(dp,-1,sizeof(dp));
	for(int i=1;i<=n;i++)
	{
		string s;
		cin>>s;
		mp[i]=dx(s);
	}
	for(int j=0;j<=cnt;j++)//处理第一行 
		if((kx[j]&mp[1])==0) dp[1][j][0]=sl[j];
	for(int j=0;j<=cnt;j++)//处理第二行 
		if((kx[j]&mp[2])==0)
			for(int k=0;k<=cnt;k++)
				if(dp[1][k][0]>=0&&(kx[j]&kx[k])==0) dp[2][j][k]=max(dp[2][j][k],dp[1][k][0]+sl[j]);
	for(int i=3;i<=n;i++)//第i行 
		for(int j=0;j<=cnt;j++)//往第i行放入状态可行j
			if((kx[j]&mp[i])==0)//确保准备要放的状态kx[j]与地形不冲突
				for(int k=0;k<=cnt;k++)//枚举第i-1行的状态
					for(int p=0;p<=cnt;p++)//枚举第i-2行的状态
						if(dp[i-1][k][p]>=0&&(kx[k]&kx[j])==0&&(kx[j]&kx[p])==0)
							 dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][p]+sl[j]);
	int mx=0;
	for(int i=0;i<=cnt;i++)
		for(int j=0;j<=cnt;j++)
			mx=max(mx,dp[n][i][j]);
	cout<<mx;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值