题目大意:
一个人在网格图中从起点’S’到终点‘T’,每次可以朝相邻八个格子前进。其中有一些格子无论如何都无法经过,有一些格子使用隐身技能后才可以通过,当然他还可以朝上下左右四个方向使用瞬移跨越多个格子。隐身和瞬移是有次数限制的。问如何在最短时间内到达目的地,且消耗技能次数最少?
[洛谷 P6474 ]
思路分析:
广度优先搜索+二维差分&前缀和。
设
(
x
,
y
,
u
,
v
)
(x,y,u,v)
(x,y,u,v)描述荆轲的状态,,表示他来到点
(
x
,
y
)
(x,y)
(x,y)时用了u次隐身,v次瞬移
有四种转移方式:
设下一步荆轲会来到
(
a
,
b
)
(a,b)
(a,b)这个点,且
(
a
,
b
)
(a,b)
(a,b)没有卫兵
(1):直接走过来:
(
x
,
y
,
u
,
v
)
−
−
>
(
a
,
b
,
u
,
v
)
(x,y,u,v)-->(a,b,u,v)
(x,y,u,v)−−>(a,b,u,v)
(2):使用隐身(在卫兵观察范围内):
(
x
,
y
,
u
,
v
)
−
−
>
(
a
,
b
,
u
+
1
,
v
)
(x,y,u,v)-->(a,b,u+1,v)
(x,y,u,v)−−>(a,b,u+1,v)
(2):直接瞬移走过来:
(
x
,
y
,
u
,
v
)
−
−
>
(
a
,
b
,
u
,
v
+
1
)
(x,y,u,v)-->(a,b,u,v+1)
(x,y,u,v)−−>(a,b,u,v+1)
(2):瞬移+隐身(在卫兵观察范围内):
(
x
,
y
,
u
,
v
)
−
−
>
(
a
,
b
,
u
+
1
,
v
+
1
)
(x,y,u,v)-->(a,b,u+1,v+1)
(x,y,u,v)−−>(a,b,u+1,v+1)
注意判断隐身和瞬移的次数是否到了上限!!!
还有一个比较麻烦的地方,就是卫兵观察范围的预处理。
画图发现,观察范围是一个斜着的正方形
比如
a
[
i
]
[
j
]
=
3
~~~a[i][j]=3
a[i][j]=3
所以我们只需要这样干:
绿色和黄色的点用来进行差分,接下来看代码
for(int i=0;i<=k;i++)
{
cov[max(x-i,1)][max(y-(k-i),1)]++; //左边+
cov[max(x-i,1)][min(y+(k-i),m)+1]--;//右-
cov[min(x+i,n)][max(y-(k-i),1)]++;//同理
cov[min(x+i,n)][min(y+(k-i),m)+1]--;
}
然后计算前缀和(差分的前缀和就是数列本身,故可以得出卫兵的监视范围)
void sum()
{
rep(i,1,n)
{
ll tj=0;
rep(j,1,m)
{
tj+=cov[i][j]; //计算前缀和
if(tj>0) f[i][j]=true;// 卫兵的监视范围内
}
}
}
全部代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
#define r register
#define rep(i,x,y) for(r ll i=x;i<=y;++i)
#define per(i,x,y) for(r ll i=x;i>=y;--i)
using namespace std;
typedef long long ll;
const int V=370;
ll n,m,c1,c2,d,sx,sy,tx,ty,cov[V][V];
ll fx[8][2]={{0,1},{0,-1},{1,0},{-1,0},{1,1},{1,-1},{-1,1},{-1,-1}};//方向数组
bool f[V][V],vis[V][V][25][25],p[V][V];
string s;
struct node
{
ll x,y,yx,sy,v;
};
node ans=(node){0,0,2147483647,2147483647,2147483647};
node cmp(node a,node b) //两条路径比较优劣
{
if(a.v!=b.v) return a.v<b.v?a:b;
if(a.yx+a.sy!=b.yx+b.sy) return a.yx+a.sy<b.yx+b.sy?a:b;
return a.yx<b.yx?a:b;
}
void build(ll x,ll y,ll k) //差分
{
rep(i,0,k)
{
++cov[max(x-i,1ll)][max(y-(k-i),1ll)];
--cov[max(x-i,1ll)][min(y+(k-i),m)+1];
++cov[min(x+i,n)][max(y-(k-i),1ll)];
--cov[min(x+i,n)][min(y+(k-i),m)+1];
}
}
bool check(ll x,ll y) //判断点是否越界
{
return (x>=1&&y>=1&&x<=n&&y<=m) ;
}
void in()
{
scanf("%lld%lld%lld%lld%lld",&n,&m,&c1,&c2,&d);
rep(i,1,n)
rep(j,1,m)
{
cin>>s;
if(s[0]=='S') sx=i,sy=j;
else if(s[0]=='T') tx=i,ty=j;
else if(s[0]!='.')
{
ll x=0,len=s.length();
rep(k,0,len-1)
x=(x<<3)+(x<<1)+(s[k]^48);
p[i][j]=true;
build(i,j,x-1);
}
}
}
void sum()
{
rep(i,1,n)
{
ll tj=0;
rep(j,1,m)
{
tj+=cov[i][j];
if(tj>0) f[i][j]=true;
}
}
}
queue<node> q;
void bfs() //本题的主角:广度优先搜索 Bfs
{
vis[sx][sy][0][0]=true;
q.push((node){sx,sy,0,0,0});
while(!q.empty())
{
node x=q.front();
q.pop();
if(x.v>ans.v) continue ;
if(x.x==tx&&x.y==ty)
{
ans=cmp(ans,x);
continue ;
}
//前面说的四种转移方式一一列举
rep(i,0,7)//不用瞬移
{
ll u=x.x+fx[i][0],t=x.y+fx[i][1];
if(!check(u,t)||p[u][t]) continue;
if(f[u][t])
{
if(x.yx>=c1||vis[u][t][x.yx+1][x.sy]) continue ;
vis[u][t][x.yx+1][x.sy]=true;
q.push((node){u,t,x.yx+1,x.sy,x.v+1});
}
else
{
if(vis[u][t][x.yx][x.sy]) continue ;
vis[u][t][x.yx][x.sy]=true;
q.push((node){u,t,x.yx,x.sy,x.v+1});
}
}
if(x.sy>=c2) continue ;
rep(i,0,3) //用瞬移
{
ll u=x.x+fx[i][0]*d,t=x.y+fx[i][1]*d;
if(!check(u,t)||p[u][t]) continue ;
if(f[u][t])
{
if(x.yx>=c1||vis[u][t][x.yx+1][x.sy+1]) continue ;
vis[u][t][x.yx+1][x.sy+1]=true;
q.push((node){u,t,x.yx+1,x.sy+1,x.v+1});
}
else
{
if(vis[u][t][x.yx][x.sy+1]) continue ;
vis[u][t][x.yx][x.sy+1]=true;
q.push((node){u,t,x.yx,x.sy+1,x.v+1});
}
}
}
}
int main()
{
in();
sum();
bfs();
if(ans.v==2147483647) printf("-1"); //ans的值没变,没有道路,输出-1
else printf("%lld %lld %lld",ans.v,ans.yx,ans.sy);
return 0;
}