状压DP详解(2)--状态的预处理+经典例题剖析--POJ1185炮兵阵地

在看本篇之前你需要对状态压缩较为理解,同时应该明白了我的前两篇博客状压0和1。
状压0https://blog.csdn.net/qq_43906000/article/details/90798220

状压1https://blog.csdn.net/qq_43906000/article/details/90815938

解决了前两篇博客的问题后可能有种状压DP不过如此的错觉,不过让你产生错觉的是状压的这一块,对于DP这一块来讲还是比较复杂的,比如接下来这一道例题:

题目链接http://poj.org/problem?id=1185

炮兵阵地

Time Limit: 2000MS Memory Limit: 65536K

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


刚开始我以为和状压0那篇博客里的题目差不多,枚举完第一行后就开始拓展,样例过了,不过WA了。。。然后发现虽然第一行固定了,但接下来的行数并不会固定。先给出WA了的代码:

//#include <bits/stdc++.h>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
char s[105][12];
int a[105][12],m,n,b[105][12];
ll dp[105][1<<12];
const int inf=99999999;
int check(int s);
int main()
{
	scanf ("%d%d",&n,&m);
	for (int i=0; i<n; i++)
	 scanf ("%s",s[i]);
	for (int i=0; i<n; i++)
	 for (int j=0; j<m; j++)
	  if (s[i][j]=='P') a[i][j]=1;
	int ans=0;
	for (int j=0; j<(1<<m); j++){
		ans=max(ans,check(j));
	}
	printf ("%d\n",ans);
	return 0;
} 
int check(int s)
{
	memset(b,0,sizeof(b));
	for (int i=0; i<m; i++){
		if ((s&(1<<i)) && !a[0][i]) return 0;
		if ((s&(1<<i)) && (s&(1<<i+1))) return 0;
		if ((s&(1<<i)) && (s&(1<<i+2))) return 0;
		else if (s&(1<<i)) b[0][i]=1;
	}
	for (int i=1; i<n; i++){
		for (int j=0; j<m; j++){
			if (i==1) {
				if (a[i][j] && !b[i-1][j]){
					if (j==0) b[i][j]=1;
					else if (j==1 && !b[i][j-1]) b[i][j]=1;
					else if (j>1 && !b[i][j-1] && !b[i][j-2]) b[i][j]=1;
				}  
			}
			else {
				if (a[i][j] && !b[i-1][j] && !b[i-2][j]){
					if (j==0) b[i][j]=1;
					else if (j==1 && !b[i][j-1]) b[i][j]=1;
					else if (j>1 && !b[i][j-1] && !b[i][j-2]) b[i][j]=1;
				}
			}
		}
	}
	int ans=0;
	for (int i=0; i<n; i++)
	   for (int j=0; j<m; j++)
	       if (b[i][j]) ans++;
	return ans;
}

和状压0的做法是一样的。标准纯正的状态压缩。。。。

所以就只能对每一行的状态进行枚举,只不过这里要进行预处理,这里枚举的最大状态为210,即1024,这么多状态进行枚举的话会T,然后我们只能进行预处理,将它所有合法的状态保存下来,然后输入极限10,发现合法的总共就60个,也就是说我们只要枚举这60个数就好了:

fr(int i=0; i<(1<<m); i++) 
    if ((i&(i<<1))==0 && (i&(i<<2))==0) {
	    v[cnt++]=i;
	    int sum=0;
	    for(int j=0; j<(1<<m); j++) if ((i&(1<<j))) sum++;
	    num[cnt-1]=sum;
    }

其中v中保存的是状态,num中保存的是该状态下的炮兵数量。

接下来就是和状压1中的方法一样了,对于每行的状态进行枚举,然后对于每行的上一行状态进行枚举,由于这里的炮兵是打两格的,所以对于上上行的状态也需要进行枚举。那么如果不预处理的话时间复杂度就是O(n* 2m * 2m *2m)。。。报表。。。

预处理也就是将2m替换为60,它的最大复杂度就是100 * 60 *60 *60而已。。。

fr (int i=0; i<n; i++) {
	fr (for (int j=0; j<cnt; j++)) {
		if (!ok(i,v[j])) continue;
		if (i==0) {
			//
			continue;
		}

这里和状压1的做法是一样的,(ok函数我相信理解了状压1的也不难写出,就先不写了)只不过将1<<m换成了cnt而已,我们先忽略怎么DP(过程用//代替),先看状态压缩。接下来是对第二行(i=1)进行处理,由于要打两行,i-2=-1,所以也要进行判断处理:

if (i==1) {
	for(int k=0; k<cnt; k++) {//判断和上一行是否01交错
		if ((v[j]&v[k])==0) //
	}
}
else {
	for(int k=0; k<cnt; k++) {//对上一行状态的枚举
		if ((v[j]&v[k])) continue;
		for(int g=0; g<cnt; g++) {//对上上行的状态枚举
			if ((v[j]&v[g]) || (v[k]&v[g])) continue;
			//
		}
	}
}

于是状态压缩的这一块就过了,现在来看DP一块,和状压1的做法差不多,但由于多了一行状态,我们也要多加一维来保存数据:dp[r][i][j],即第r行的第i个状态对应的上一个状态为j在炮兵的值,相对于玉米田只隔了一格的dp[r][i]来讲,也就只多了一维的枚举而已。但由于我们不是要的方案总数,所以只需要取出一个最大值就好了,也就是说:

dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][g]+num[j]);

这个就是核心的dp公式了,即第i行的第j种状态的上一个状态为k的值为他本身和第i-1行的状态为k的上一个状态为g的值+第j种状态的数量的最大值。那么对于第一行和第二行稍微处理一下也出来了:

dp[i][j][0]=num[j];//第一行
dp[i][j][k]=max(dp[i-1][k][0]+num[j],dp[i][j][k]);//第二行

以下是AC代码:

//#include <bits/stdc++.h>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define fr(i,n) for (int i=0; i<n; i++)
using namespace std;
typedef long long ll;
char s[105][12];
int a[105][12],m,n,dp[102][65][65];
int v[65],num[65],ans=0;
int ok(int r,int s);
int main()
{
	scanf ("%d%d",&n,&m);
	memset(dp,0,sizeof(dp));
	int cnt=0;
	fr(i,(1<<m)) if ((i&(i<<1))==0 && (i&(i<<2))==0){
		v[cnt++]=i;
		int sum=0;
		fr(j,m) if ((i&(1<<j))) sum++;
		num[cnt-1]=sum;
	}
	fr(i,n) scanf ("%s",s[i]);
	fr(i,n) fr(j,m) if (s[i][j]=='P') a[i][j]=1; 
	fr (i,n){
		fr (j,cnt){
			if (!ok(i,v[j])) continue;
			if (i==0){
				dp[i][j][0]=num[j];
				continue;
			}
			if (i==1){
				fr(k,cnt){
					if ((v[j]&v[k])==0) dp[i][j][k]=max(dp[i-1][k][0]+num[j],dp[i][j][k]);
				}
			}
			else {
				fr(k,cnt) {
					if ((v[j]&v[k])) continue;
					fr(g,cnt){
						if ((v[j]&v[g]) || (v[k]&v[g])) continue;
						dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][g]+num[j]);
					}
				}
			}
		}
	}
	fr(i,cnt) fr(j,cnt) ans=max(ans,dp[n-1][i][j]);
	printf ("%d\n",ans);
	return 0;
} 
int ok(int r,int s)
{
	fr(i,m) if ((s&(1<<i)) && !a[r][i]) return 0;
	return 1;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值