炮兵阵地
Description 司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队。一个N*M的地图由N行M列组成,地图的每一格可能是山地(用"H" 表示),也可能是平原(用"P"表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。 现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。 Input 第一行包含两个由空格分割开的正整数,分别表示N和M; 接下来的N行,每一行含有连续的M个字符('P'或者'H'),中间没有空格。按顺序表示地图中每一行的数据。N <= 100;M <= 10。 Output 仅一行,包含一个整数K,表示最多能摆放的炮兵部队的数量。 Sample Input 5 4 PHPP PPHH PPPP PHPP PHHP Sample Output 6 |
Time Limit: 2000MS |
| Memory Limit: 65536K |
Total Submissions: 27638 |
| Accepted: 10706 |
原题链接
看到n的范围有100,但是m的范围只有10.
我们可以考虑每一行的状态:放下炮兵的位置是1,没放的是0.
那么每一行连起来可以得到一个二进制串,且其十进制大小在[0,1023]。
比如PPPP,在无视题目要求的情况下,我们看到,1101就是表示1,2,4的位置放了炮兵。
而1101对应10进制是13,我们可以在dp的时候用13来表示这个状态。
这也就是我们所谓的状态压缩dp。
再看看这题:明显的是,我们可以从每一行入手,因为m的范围非常小。
每一个炮兵在横向的地方,可以攻击左右2格子内的位置。
比如说,上面的PPPP,1101的方案不可行了。
我们发现虽然二进制下的值可能会比较大,但是状态的个数却没有多少。
就算m=10,PP..P,通过简单估算也就只有30多种。
所以我们可以用一个dfs的过程来初始化所有状态。
那么如何DP呢?
我们看到其实每一行的状态,前两行的决策是会有影响的。
简单地说,每3个连续行,是我们要知道的dp状态。
下面是dp正文:
Dp[u][Sta1][Sta2]表示第u行,且第u行的压缩状态是Sta1,第(u-1)行的压缩状态是Sta2的最大炮兵个数。
那么我们每次需要枚举第u行的状态Sta1,以及第(u-1)行的状态Sta2,(u-2)行的状态Sta3.
在这之前,我们已经用一个dfs将每一行的可行状态预处理出来了。所以行的地方都是满足条件的。
如何判断列的方向没有冲突?
很显然,没有相交的1,就是合法的。
比如1000,0100,0010就合法,而
1000,0100,1000就不合法。
这个过程我们通过位运算解决:Sta1 and Sta2=0且Sta2 and Sta3=0且Sta1 and Sta3=0
方程:Dp[u][Sta1][Sta2]=max(Dp[u][Sta1][Sta2],Dp[u-1][Sta2][Sta3]+num(Sta1));
其中,num(x)是统计x的二进制中1的个数(应该很好理解)
这个地方也可以初始化一起掉了的其实。
还有几点比较重要:
1.初始化
我们知道需要向前枚举2行,那么处理base case的时候,要小心。
2.答案统计
很显然,答案就是从第n行和第(n-1)的地方枚举所有状态然后判断最大值。
但是n=1会怎么样?
我们知道第0行的状态是空的。
所以这个时候特殊判断就好了。
感觉自己打的略有繁琐。。加死各种优化还是要450+ms。。。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,g[15],f[2][1030][1030];
int cnt[105],a[105][1030],p[105][1030];
char s[105][15];
inline int read(){
int x=0,f=1;char ch=getchar();
while (ch<'0' || ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline int count(int ii,int j){
int tmp=0,t=1;
for (int i=1;i<=m;i++){
tmp+=t*g[i];
if (g[i]) a[ii][j]++;
t<<=1;
}
return tmp;
}
void dfs(int now,int i){
if (now>m){
p[i][++cnt[i]]=count(i,cnt[i]);
return;
}
if (!g[now-1] && (now<2 || !g[now-2]) && s[i][now-1]=='P'){
g[now]=1;
dfs(now+1,i);
g[now]=0;
}
dfs(now+1,i);
}
int main(){
n=read(),m=read();
for (int i=1;i<=n;i++)
scanf("%s",s[i]);
memset(cnt,0,sizeof(cnt));
memset(a,0,sizeof(a));
memset(g,0,sizeof(g));
for (int i=1;i<=n;i++)
dfs(1,i);
for (int i=1;i<=cnt[1];i++)
for (int j=0;j<=1024;j++)
f[1][p[1][i]][j]=a[1][i];
for (int i=1;i<=cnt[2];i++)
for (int j=1;j<=cnt[1];j++)
for (int k=0;k<=1024;k++)
if (!(p[0][i]&p[1][j]))
f[0][p[2][i]][p[1][j]]=max(f[0][p[2][i]][p[1][j]],f[1][p[1][j]][k]+a[2][i]);
for (int i=3;i<=n;i++)
for (int j=1;j<=cnt[i];j++)
for (int k=1;k<=cnt[i-1];k++)
for (int t=1;t<=cnt[i-2];t++)
if ((!(p[i][j]&p[i-1][k])) && (!(p[i][j]&p[i-2][t])) && (!(p[i-1][k]&p[i-2][t])))
f[i&1][p[i][j]][p[i-1][k]]=max(f[i&1][p[i][j]][p[i-1][k]],f[(i+1)&1][p[i-1][k]][p[i-2][t]]+a[i][j]);
int ans=0;
if (n==1){
for (int i=1;i<=cnt[1];i++)
ans=max(ans,a[1][i]);
} else
for (int i=1;i<=cnt[n];i++)
for (int j=1;j<=cnt[n-1];j++)
ans=max(ans,f[n&1][p[n][i]][p[n-1][j]]);
printf("%d\n",ans);
return 0;
}