POJ 1185 炮兵阵地 状态压缩DP

炮兵阵地

Time Limit: 2000MS


Memory Limit: 65536K

Total Submissions: 27638


Accepted: 10706

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


看到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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值