这里是引用
问题描述
一个叫Andy的小朋友在一个正方形的沙漠中迷路了,身边的水也喝完了。Andy知道自己要渴死了,闲得无聊,所以他开始计算一些有趣的问题。
这个沙漠是由一个n*n的网格图构成的,当你站在某一个格子时,你可以走向周围八个格子中的一个。然而,沙漠中的某些格子有毒蛇,你必须避开他们,某些格子有水源,水源就是生命。
现在,Andy希望计算出每个格子到最近的水源的距离。你只需计算出所有距离的和即可。
输入格式
输入的第一行包含三个整数n, m,p,n表示沙漠的边长。m表示毒蛇的个数,p表示绿洲的个数。
接下来m行,输入m个毒蛇的坐标。一行一个
在接下来p行,输入p个绿洲的坐标。一行一个
输出格式
输出一个数,表示沙漠中每个坐标上避开毒蛇,找到绿洲的最短路径长度之和。
若坐标上有毒蛇,或无法从此坐标出发找到绿洲,长度记为-1.
若此坐标上有绿洲,长度记为0.
样例输入
3 1 2
2 1
1 3
3 2
样例输出
6
评测用例规模与约定
对于60%的数据,0<p<=5。
对于100%的数据,0<n<=3000, 0<p<=3000。
- 第一步,分析题目,扩散问题(广搜最好用)。
- 如果考虑从每个空白方格开始广搜,太慢了,因为空白方格个数>>水源个数,所以应该从水源开始搜。
- 我们不难发现每次从水源往外搜的时候可以确定每个空白方格的距离(是从当前水源得出,所以不一定是最终结果),因此需要p次广搜,然后比较每个经过的空白方格的大小,取最小。
- 但是敲出代码(普通bfs,就不贴了),发现AC不了,只过了60%,也就是只过了p<=5的情况。所以想到bfs优化剪枝,用一个二维数组存储(i,j)的结果,如果下次走到(i,j)发现步数大于了这个二维数组存储的值(也即是以前走过的时候值),就不进队了,相当于可行性剪枝,代码如下:(60%)
#include <bits/stdc++.h>
#include <algorithm>
#define ll long long
#define ssm stringstream
#define Swap(a,b) {int t = a;a = b;b = t;}
using namespace std;
const int INF = 99999999;
int a[3005][3005] = {0};
int vis[3005][3005];
struct node{
int x,y,step;
};
int n,m,p;
int dir[2][8] = {-1,-1,-1,0,0,1,1,1,-1,1,0,-1,1,-1,1,0};
int res[3005][3005]; //这个就是记录以前走过的值的二维数组,用来剪枝。
bool Valid(int x,int y){
return x >= 1 && x<=n && y>=1 && y<=n;
}
void bfs(node g){
memset(vis,0,sizeof(vis));
queue<node> q;
q.push(g);
vis[g.x][g.y] = 1;
while(!q.empty()){
int size = q.size();
while(size--){
g = q.front();
q.pop();
for(int i=0;i<8;++i){
int nx = g.x + dir[0][i];
int ny = g.y + dir[1][i];
if(Valid(nx,ny) && !vis[nx][ny] && !a[nx][ny]){
if(res[nx][ny] > g.step + 1){//在这里剪枝,如果发现之前走过的没有这次短,就走过来继续,否则就不搜了。
res[nx][ny] = g.step + 1;
vis[nx][ny] = 1;
q.push({nx,ny,g.step + 1});
}
}
}
}
}
}
int main(){
int x,y;
cin>>n>>m>>p;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
res[i][j] = INF;
vector<node> record;
while(m--)
cin>>x>>y, a[x][y] = 1,res[x][y] = -1;
while(p--)
cin>>x>>y, a[x][y] = 2,res[x][y]=0,record.push_back({x,y,0});
for(int i = 0;i<record.size();++i)
bfs(record[i]);
ll ans = 0;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(res[i][j] == INF )
res[i][j] = -1;
ans += res[i][j];
}
// cout<<endl;
}
cout<<ans<<endl;
return 0;
}
- 提交后发现还是60%,由此可见可行性剪枝不够优化,于是想到动态规划,发现~~没法用动态规划啊这!
- 最后想起来不是有个A算法和IDA算法吗,发现也不好使!dfs不好解决此问题,再次想到双向广搜,可是,这么多水源,多达3000个,双向广搜不好使啊,于是有个大胆的想法!----3000向广搜。于是抱着试一试的心态写出来了如下代码,竟没想到AC了~
(AC代码)
//已注释的地方是我Debug 的时候写的,请忽略~~还有那堆for循环,自己偷懒,虽然我也不用他
#include<bits/stdc++.h>
#define ll long long
#define ssm stringstream
#define Swap(a,b) {int t = a;a = b;b = t;}
#define Loop10(tag,a) for(int tag=0;tag<a;++tag)
#define Loop11(tag,a) for(int tag=1;tag<=a;++tag)
#define Loop21(a,b) for(int i=1;i<=a;++i) for(int j=1;j<=b;++j)
#define Loop20(a,b) for(int i=0;i<a;++i) for(int j=0;j<b;++j)
using namespace std;
const int MAXN = 3005;
const int INF = 0x3f3f3f3f; //代表无穷大
int a[MAXN][MAXN];
int tag[MAXN][MAXN] = {0}; //3000向广搜的标志位
int n,m,p;
bool Valid(int x,int y){
return x>=1&&y>=1&&x<=n&&y<=n;
}
int dir[2][8] = {-1,-1,-1,0,0,1,1,1,0,1,-1,1,-1,0,1,-1};
struct node{
int x,y,step;
};
queue<node> q[3004]; //3000个广搜队列
bool Ok(){
for(int i=0;i<p;++i){
if(q[i].size())
return true;
}
return false;
}
void bfs(){ //核心代码
node g ;
while(Ok()){
for(int i=0;i<p;++i){
if(!q[i].empty()){
int size = q[i].size();
while(size--){
g = q[i].front();
q[i].pop();
for(int j=0;j<8;++j) {
int nx = g.x + dir[0][j];
int ny = g.y + dir[1][j];
if(Valid(nx,ny) && a[nx][ny]!=-1&&a[nx][ny]!=0){
if(tag[nx][ny] != -1){//这里判断是否q[i]搜索到的点被其他队列经过过,如果有就不搜了。
a[nx][ny] = min(a[nx][ny],g.step+1);
continue;
}
tag[nx][ny] = i;
a[nx][ny] = 1 + g.step;
node gg ;
gg.x = nx;
gg.y = ny;
gg.step = 1 + g.step;
q[i].push(gg);
// printf("q[%d].push(%d,%d)\n",i,nx,ny);
}
}
}
}
}
}
}
int main(){
cin>>n>>m>>p;
int x,y;
memset(a,0x3f3f3f3f,sizeof(a));
memset(tag,-1,sizeof(tag));
for(int i=0;i<m;++i){
cin>>x>>y;
a[x][y] = -1;
}
for(int i=0;i<p;++i){
cin>>x>>y;
a[x][y] = 0;
node g;
g.x = x;
g.y = y;
g.step = 0;
q[i].push(g);
}
bfs();
ll res = 0;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(a[i][j] == INF) //别忘了这里,如果走不到这里,要置为-1,因为我一开始把所有点都置为无穷大了。
a[i][j ] = -1;
// cout<<a[i][j]<<' ';
res += a[i][j];
}
// cout<<endl;
}
cout<<res<<endl; //打印结果咯
return 0;
}
美滋滋,这一题的难度还是不错的,一道题写一天。暴力才是最舒服的 ,但是吃不全分啊,干了兄弟们。