2020 [NOI Online #2 入门组] 荆轲刺秦王 [luogu P6474]

题目大意:

一个人在网格图中从起点’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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值