简易炸弹超人 题解(蓝桥杯中级组2023选拔赛)

题外话

本人第一次写文章,有写的不好的地方请提出,关于文章内容有什么不懂的也可以问我!
~~大佬及虐菜人士请自觉离开 (小声BB:这应该是全CSDN第一篇炸弹超人题解吧?)

题目描述

有一块矩形游戏场地,场地被分成 NxM 的网格(4N<100,4M<10)其中一部分小方格是水域,另一部分小方格是陆地。
为防御敌军攻击,玩家需要在游戏场地安置炸弹:
1.炸弹只能安置在陆地上;
2.每颗炸弹爆炸后,可以波及到炸弹所在的小方格,及相邻的上、下、左、右小方格;
3.任意两颗炸弹爆炸后不能波及到同一个小方格
请帮助玩家计算出如何安置炸弹,可以使炸弹波及到的范围最大,输出最多可以波及到的小方格数量。
例如:N=4,M=4,网格中水域和陆地的情况如图1所示:
Alt
图中,蓝色区域代表水域,绿色区域代表陆地:
安置炸弹的最优方案之一如图2所示;
炸弹波及的范围如图3所示(黑色区域);
这块 4x4 的矩形游戏场地最多可以波及到 11个小方格,其他方案都不会优于这个结果。

输入

第一行输入两个正整数 N和M (4<N<100,4<M<10)
分别表示网格的行数和列数,两个正整数之间以一个空格隔开
接下来输入N行,每行 M 个字符 (字符只能是大写字母 A或 B),
A 表示水域,B表示陆地,字符之间以一个空格隔开

输出

输出一个整数,表示最多可以波及到的小方格数量

样例

样例输入:
44
BAAA
ABAB
BABB
ABAA
样例输出 :
11

题目分析

重点分析:
因为两个炸弹不能波及到同一个方格,所以一个炸弹能够占据的方格是?
炸弹图例

且炸弹只能放在陆地上,而且要合法.
注:(算波及格子数时别忘记判断是否越界) *(血与泪的教训 *

所以,这是一道数据很狗的题目
大家拿到这题刚开始按之前选拔赛的标准肯定和我当时一样,觉得直接暴力枚举+搜索即可骗分大法好
但是,这题会爆内存:

dfs,bfs? -不能AC!
普通dp? -炸裂开来???
状压dp? -也还不够???!!!
那该咋整????????????????

答:做状压dp的优化

逻辑框架和具体实现

这题我们可以用二进制来标记该位置是否为水域*(0是陆地,1是水)*
是否安装过炸弹同理*(1安装过了,0没有)*
所以,记录该位置是否为水域为G[i],该位置是否安装过为S[i],
知识点:“与”运算(&)和“移位”(<<和>>)

这里有个小知识点:二进制中的常规运算
这里就不细讲了,具体可以看:基础二进制运算
就是用来判断是否合题意:
当G[i]&s[i]=0时就会符合题意,反之则不符
关于是否合法,只需要保证一开始那张图标记的红色方格上没有炸弹即可

即不能有相邻“1”的间隔 < 2格(s&s>>1=0,s&s>>2=0
且相邻3行不冲突(设第i,i-1,i-2行状态为a,b,c;只需a&b,a&c,b&c都为0即可)

状态定义

dp[i][j][k]:第i行炸弹安装状态为j,且第i-1行的状态为k,能波及到的最大格子数
cnt(i,s)函数:在第i行状态为s的情况下,有多少新添的炸毁格子数
则很容易推出和背包问题类似的状态转移方程:dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][u]+cnt(i,s))
==优化原因:如果不优化,时间复杂度为O(Nx2的2M次方),会炸,所以只能搞滚动数组
==

上代码

#include<iostream>
#include<vector>
using namespace std;

const int N=110,M=1<<10;
int n,m;
int g[N],cnt[M],dp[2][N][M];
vector<int>s,h[M];

//判断条件:
bool istrue(int s){
	return!(s&s>>1||s&s>>2);
}
bool istrue2(int s){
	return s&s>>1;
}
int cntl(int x,int s){
	int cnt=0;
	int flag=0;
	for(int i=0;i<=m;i++){
			if(s>>i&1){
				flag=1;
				if(x-1>=1)cnt++;
				if(x+1>=n)cnt++;
				if(x-1>=0)cnt++;
				if(x+1>=m)cnt++;
			}
		}
	return cnt+flag;
}

int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=0;j<m;j++){
			char x;
			cin>>x;
			if(x=='A') g[i]+=1<<j;
			}
		}
	for(int i=0;i<1<<m;i++){
		if(check(i)) s.push_back(i);//找出合法的状态
	}
	for(int i=1;i<=n+2;i++){
		for(int j=0;j<s.size();j++){//i
			for(int k=0;k<s.size();k++){//i-1
				for(int u=0;u<s.size();u++){//i-2
					int a=s[u];
					int b=s[k];
					int c=s[i];//前面说的,用a,b,c表示三行状态
					if((a%b)||(b&c)||(a&c)) continue;
					if((g[i]&c||g[i-1]&b)) continue;
					if(istrue2(a|b)) continue;
					int cnt=i<=n/cnt1(i,c):0;
					dp[i&1][j][k]=max(dp[i&1][j][k],dp[i-1&1][k][u]+cnt);
				}
			}
		}
	}
	cout<<f[n+2&1][0][0]<<endl;
	return 0;
}

-2023年4月15日

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值