输入格式
第一行输两个整数 n,mn,m,用空格隔开。
接下来 nn 行,每行输入一个长度为 mm 的字符串,表示地图信息。0表示没有炸弹,1表示炸弹。
数据约定:
对于 60%60% 的数据:1 \le n, m \le 1001≤n,m≤100;
对于 100%100% 的数据:1 \le n, m \le 10001≤n,m≤1000;
数据量比较大,不建议用cin输入。
输出格式
输出一个整数,表示最少需要手动引爆的炸弹数。
样例输入
5 5
00010
00010
01001
10001
01000
样例输出
2
题目链接
由于题目数据量的限制,暴力求解肯定是不可能的,分析一下可以发现,这道题目和经典的连通块问题很相似,所以可以使用dfs求解。自己在做这道题的时候没想到怎么使用dfs,所以使用的并查集,也可以解决,后来看了网上dfs的思路,发现dfs简单多了,所以先列出dfs的做法。
ps:注意剪枝,不然超时会蹦出来的。
#include
using namespace std;
typedef long long ll;
const int maxn=1010;
const int INF=0x3f3f3f3f;
int n,m;
int _map[maxn][maxn],vis[maxn][maxn];
//_map存储输入 vis判断某个点是否访问过(即炸弹是否已经引爆)
int c_judge[maxn],l_judge[maxn];
//剪枝使用 c_judge判断某一行的炸弹是否已经引爆,l_judge代表列
void dfs(int x,int y)
{
vis[x][y]=1;
if(!c_judge[x])// 如果该点所在的行没有被引爆过,要将这一行全部引爆
{
c_judge[x]=1;
for(int i=0; i<m; i++)
{
if(_map[x][i]==1&&!vis[x][i])//如果该点是炸弹,并且为引爆
dfs(x,i);//则以该点为起点惊醒dfs
}
}
if(!l_judge[y])//列的操作同上
{
l_judge[y]=1;
for(int i=0; i<n; i++)
{
if(_map[i][y]==1&&!vis[i][y])
dfs(i,y);
}
}
return;
}
int main()
{
memset(vis,0,sizeof(vis));
memset(l_judge,0,sizeof(l_judge));
memset(c_judge,0,sizeof(c_judge));
memset(_map,0,sizeof(_map));// 初始化
cin>>n>>m;
string s;
for(int i=0; i<n; i++)
{
cin>>s;
for(int j=0; j<m; j++)
_map[i][j]=s[j]-'0';
}
int cnt=0;
for(int i=0; i<n; i++)
{
for(int j=0; j<m; j++)
{
if(_map[i][j]==1&&!vis[i][j])//遍历每一个炸弹,
// 如果没有被访问过说明要多手动引爆一次
{
cnt++;
dfs(i,j);
}
}
}
cout<<cnt<<endl;
}
使用并查集相较dfs比较麻烦,类比连通块,将相互联通的炸弹划到同一个集合。这里的思路是将行列存到同一个数组中,将某一个炸弹的行列划分到一个集合,将同一行或者同一列划分到同一集合,最后存在多少个集合即为结果。
#include
using namespace std;
typedef long long ll;
const int maxn=2020;
const int INF=0x3f3f3f3f;
int n,m;
int root[maxn],_map[maxn][maxn];
int find_root(int x)// 查找根节点
{
int x_root=x;
while(x_root!=root[x_root])
x_root=root[x_root];
int l=x,r;// 找到根节点后,将该路径上的所有炸弹的根节点全部改为最初始的根节点
//这个地方很重要,不把所有的该路径上的炸弹的根节点更新的话会超时
//就是因为这个地方卡了很长时间
while(l!=x_root)
{
r=root[l];
root[l]=x_root;
l=r;
}
return x_root;
}
int high[maxn];// 记录某个集合的“深度”
void union_tree(int x,int y)
{
int x_root=find_root(x);
int y_root=find_root(y);
if(x_root!=y_root)
{
// 为了加快find_root的速度,合并集合的时候要将
// 较“浅”的集合添加到较“深”的集合上
// 不过看网上的程序,这儿直接合并,不进行此优化也可以
if(high[x_root]>high[y_root])
{
root[y_root]=x_root;
high[y_root]++;
}
else
{
root[x_root]=y_root;
high[x_root]++;
}
}
}
int main()
{
ios::sync_with_stdio(false);
for(int i=0; i<maxn; i++)
root[i]=i;
cin>>n>>m;
string s;
memset(high,0,sizeof(high));
for(int i=0; i<n; i++)
{
cin>>s;
for(int j=0; j<m; j++)
{
_map[i][j]=s[j]-'0';
if(s[j]=='1')// 这里前n项存行,后面的存列
union_tree(i,j+n);// 将一个炸弹的行和列划分到同一个集合
}
}
map<int,int> judge;// 判断某个根节点是否是新的根节点
int cnt=0,k;
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
if(_map[i][j]==1)
{
k=find_root(i);// 先找某一行的根节点
if(!judge[k])
{
cnt++;
judge[k]=1;
}
k=find_root(j+n);// 再找某一列的根节点
if(!judge[k])
{
cnt++;
judge[k]=1;
}
}
}
}
cout<<cnt<<endl;
}