炮兵阵地
题目描述
题意解释
条件:
- 山地上不能部署炮兵部队
- 沿横向左右两格,沿纵向上下两格
题目问“最多”,是求最大值。、
我们用1来表示平原,用0来表示山地。
核心思路
如何理解下面这段代码呢?
for(int i=1;i<=n;i++)
{
for(int j=0;j<m;j++)
{
char c;
cin >>c;
if(c=='P')
g[i]+=1<<(m-j-1); //保存地图各行的数值
}
}
如下图所示
第一行输入PHHP,即1001,换算成十进制数就是9,所以g[1]=9。
第二行输入HPHP,即0101,换算成十进制数就是5,所以g[2]=5
第三行输入HHPH,即0010,换算成十进制数就是2,所以g[3]=2
如何理解s数组和num数组呢?
使用滚动数组优化:
int f[N][M][M];
如果这么写就会爆炸空间了, 110 ∗ 1024 ∗ 1024 ∗ 4 = 440 M B > 64 M B 110*1024*1024*4=440MB>64MB 110∗1024∗1024∗4=440MB>64MB,我们从更新代码发现,第i行的状态只与第i-1行的状态有关,所以第一维只开2个空间就行了。
2 ∗ 1024 ∗ 1024 ∗ 4 = 8 M B < 64 M B 2*1024*1024*4=8MB<64MB 2∗1024∗1024∗4=8MB<64MB,因此可以使用滚动数组来优化,这样就不会MLE了。
下图左边是没有使用滚动数组,右边是使用滚动数组。
为什么还需要考虑(s[b]&g[i-1])==s[b]呢?
因为我们是先固定好第i行和第i-1行,然后再去判断第i-2行。因此,必须先让第i行和第i-1行满足两个限制条件才可以。因此还要加上 ( s [ b ] & g [ i − 1 ] ) = = s [ b ] (s[b]\&g[i-1])==s[b] (s[b]&g[i−1])==s[b]。
为什么是1<<(m-j-1)呢?
因为比如输入PHPP,二进制就是1011。那么从前往后输入其实就是从高位到低位,但是我们枚举的j是从低位到高位,因此使用m-j-1就可以表示从高位到低位了,这样相加时,就不会出错了,至于为什么减去1,是因为j从0开始枚举,如果是从1开始枚举,则可以写成m-j的形式。
for(int i=1;i<=n;i++)
{
for(int j=0;j<m;j++)
{
char c;
cin >>c;
if(c=='P')
g[i]+=1<<(m-j-1);
}
}
//或者这么写
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
char c;
cin >>c;
if(c=='P')
g[i]+=1<<(m-j);
}
}
对于合法状态要与地形适应的解法:
一般都是用 ( s [ a ] & g [ i ] ) = = s [ a ] (s[a]\&g[i])==s[a] (s[a]&g[i])==s[a]来判断,如果为真,则说明合法状态与地形是相适应的,否则就不是。但是使用这个公式的前提是,我们要明确【合法状态】中’1’的含义和【地形】中’1’的含义。
代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=110,M=1<<10;
int n,m;//行数、列数
//比如0000,0001,0010,0100,1000,1001就是合法状态,那么cnt=6
int cnt;//同一行的所有合法状态的个数
int g[N]; //g[i]表示第i行的状况,保存地图各行数值,比如第二行输入PPHH,则为1100,所以g[2]=12
int s[M];
//int s[M];//存储合法状态的集合,比如s={0000,0001,0010,0100,1000,1001}
//比如对于0000,那么num[0]=0;对于0001,num[1]=1;对于0010,num[2]=1;对于0100,num[3]=1;对于1000,num[4]=1
//对于1001,num[5]=2
int num[M];//统计每个合法状态中含有1的个数
//如果这么写就会爆炸空间了,110*1024*1024*4=440MB>64MB
//int f[N][M][M];
//使用滚动数组优化 我们从更新代码发现,第i行的状态只与第i-1行的状态有关,所以第一维只开2个空间就行了
//2*1024*1024*4=8MB<64MB
//f[i][a][b]表示已经放好了前i行,第i行第a个状态,第i-1行第b个状态时,能放置的最大数量
int f[2][M][M];
int main()
{
scanf("%d%d",&n,&m);//输入行数和列数
//预处理地图
for(int i=1;i<=n;i++)
{
for(int j=0;j<m;j++)
{
char c;
cin >>c;
if(c=='P')//把平原P看作是1,把山地H看作是0
g[i]+=1<<(m-j-1); //保存地图各行数值
}
}
//预处理每行的所有合法状态,并统计某个合法状态中有多少个1
for(int i=0;i<(1<<m);i++)
{
if(!(i&i>>1)&&!(i&i>>2))//如果不存在11或者101之类的
{
s[cnt++]=i;//保存一行的合法状态
for(int j=0;j<m;j++)
num[i]+=(i>>j&1); //统计每个合法状态包含1的个数
}
}
f[0][0][0]=0;
//从第1行枚举到第n+2行
for(int i=1;i<=n+2;i++)
{
for(int a=0;a<cnt;a++)//枚举第i行的合法状态
{
for(int b=0;b<cnt;b++)//枚举第i-1行的合法状态
{
for(int c=0;c<cnt;c++)//枚举第i-2行的合法状态
{
if(!(s[a]&s[b])&&!(s[a]&s[c])&&!(s[b]&s[c])&&(s[a]&g[i])==s[a]&&(s[b]&g[i-1])==s[b])
{
//由于f数组的第一维只有两个空间,因此第一维空间下标只能是0和1,不能是2
//那么我们可以对2取模,这样就可以保证得到的结果是小于2的了,即0和1
//i&1就相当于i%2 减法优先级高于与运算
f[i&1][a][b]=max(f[i&1][a][b],f[i-1&1][b][c]+num[s[a]]);
}
}
}
}
}
cout <<f[n+2&1][0][0]<<endl;
return 0;
}