AtCoder abc273. F - Hammer 2 题解

题目描述

在坐标系中,有 n n n 个墙与 n n n 个墙
h [ i ] h[i] h[i] 表示第 i i i 个锤子的位置
w [ i ] w[i] w[i] 表示第 i i i 个墙的位置
想要通过 i i i 个墙,必须拿到第 i i i 个锤子
从原点开始,到底目标点 x x x, 求最少的路程,如果到达不了就输出 − 1 -1 1

输入样例
3 10
-2 8 -5
5 -10 3
输出样例
40
输入样例
5 -1
10 -20 30 -40 50
-10 20 -30 40 -50
输出样例
1
输入样例
1 100
30
60
输出样例
-1
输入样例
4 865942261
703164879 -531670946 -874856231 -700164975
-941120316 599462305 -649785130 665402307
输出样例
4078987507

算法 (区间dp)

时间复杂度 O ( n 2 ) O(n^2) O(n2)
状态定义

f [ i ] [ j ] [ 0 ] f[i][j][0] f[i][j][0] 表示现在已经走过了区间 [ i , j ] [i,j] [i,j] ,并且现在在位置 j j j 所需要的代价最小值
f [ i ] [ j ] [ 1 ] f[i][j][1] f[i][j][1] 表示现在已经走过了区间 [ i , j ] [i,j] [i,j] ,并且现在在位置 i i i 所需要的代价最小值

因为在这里,区间可以是负数,所以我们需要离散化,并且建立映射,找到第一个开始的点与结束的点。

离散化
node.push_back(-1e9-10);
node.push_back(1e9+10);
node.push_back(0);
node.push_back(x);
for(int i=1;i<=n;i++)
{
	scanf("%d",&wall[i]);
	node.push_back(wall[i]);
	mp1[wall[i]]=i;//mp1是墙与idx的映射
}
for(int i=1;i<=n;i++)
{
	scanf("%d",&ham[i]);
	node.push_back(ham[i]);
	mp2[ham[i]]=i;//mp2是锤子与idx的映射
}
sort(node.begin(),node.end());
int st,ed,sz=node.size()-2;
for(int i=0;i<=sz;i++)
{
	if(node[i]==0) st=i;
	if(node[i]==x) ed=i;
}
mp2[x]=n+1;//之后会用到

那么我们最终的状态定义就是这样的
f [ i ] [ j ] [ 0 ] f[i][j][0] f[i][j][0] 表示现在已经走过了第 i i i 个节点到第 j j j 个节点 ,并且现在在位置 第 j j j 个节点所需要的代价最小值
f [ i ] [ j ] [ 1 ] f[i][j][1] f[i][j][1] 表示现在已经走过了第 i i i 个节点到第 j j j 个节点 ,并且现在在位置 第 i i i 个节点所需要的代价最小值

f [ i ] [ j ] [ 0 ] f[i][j][0] f[i][j][0] 可以更新 f [ i ] [ j ] [ 1 ] f[i][j][1] f[i][j][1]
更新方法直接从第 i i i 个节点 走到第 j j j 个节点

f[i][j][1]=min(f[i][j][1],f[i][j][0]+node[j]-node[i]);

f [ i ] [ j ] [ 0 ] f[i][j][0] f[i][j][0] 可以更新 f [ i − 1 ] [ j ] [ 1 ] f[i-1][j][1] f[i1][j][1]
i − 1 i-1 i1 个点有三种情况
1. 是锤子
那么可以直接拿起来走
2. 是终点
那么可以走(这么看的话有点麻烦,所以在后面为了方便,设置 x x x 上有一个锤子)
即 mp2[x] = n+1;
3. 是墙
如果想走到这里的话,我们必须有第 i − 1 i-1 i1 号锤子
在这里我们用前缀和的思想来判断它有没有锤子
p r e [ i ] [ j ] pre[i][j] pre[i][j] 表示走到第 i i i 号节点,是否拥有第 j j j 类型的锤子
那么 p r e [ i ] [ j ] pre[i][j] pre[i][j] 的更新如下

for(int i=1;i<=sz;i++)
{
	for(int j=1;j<=n;j++)
	{
		pre[i][j]=pre[i-1][j];//前i个点哪些锤子出现了
		if(mp2[node[i]])//是锤子
		{
			pre[i][mp2[node[i]]]++;
			isham[i]=1;
		}
		else
		{
			pt[i]=mp1[node[i]];//墙的种类
		}
	}
}

那么如果我们在 [ i , j ] [i,j] [i,j] 之间有第 i − 1 i-1 i1 号锤子就可以走到 i − 1 i-1 i1 号墙

综上所述 由 i i i -> i − 1 i-1 i1 更新方法如下

if(isham[i-1]||(pre[r][pt[i-1]]-pre[i-1][pt[i-1]]>0))
{
	f[i-1][j][0]=min(f[i-1][j][0],f[i][j][0]+node[i]-node[i-1]);
}

同理可以得到由 j j j -> j + 1 j+1 j+1 的更新方法

最终更新方程为

f[i][j][0]=min(f[i][j][0],f[i][j][1]+node[j]-node[i]);
f[i][j][1]=min(f[i][j][1],f[i][j][0]+node[j]-node[i]);
//i->往左走走到i-1
//假设要么是墙要么是锤子
//是锤子或者是墙然后有对应的锤子
if(isham[i-1]||(pre[j][pt[i-1]]-pre[i-1][pt[i-1]]>0))
{
	f[i-1][j][0]=min(f[i-1][j][0],f[i][j][0]+node[i]-node[i-1]);
}
//i->往右走走到i+1
if(isham[j+1]||(pre[j][pt[j+1]]-pre[i-1][pt[j+1]]>0))
{
	f[i][j+1][1]=min(f[i][j+1][1],f[i][j][1]+node[j+1]-node[j]);
}
C++ 代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<unordered_map>
#include<unordered_set>
#include<cmath>
#include<map>
#include<set>
#include<stack>
#include<vector>
#include<deque>
#include<cmath>
#include<ctime>

using namespace std;

const int N=3010;
typedef long long ll;
ll f[N][N][2];
int wall[N],ham[N],pre[N][N];
int iswall[N],isham[N];
int pt[N];//墙的种类
typedef pair<int,int> pii;
#define x first
#define y second
vector<int> node;
unordered_map<int,int> mp1,mp2;
ll res=9e18;

int main()
{
	int n,x;
	cin>>n>>x;
	node.push_back(-1e9-10);
	node.push_back(1e9+10);
	node.push_back(0);
	node.push_back(x);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&wall[i]);
		node.push_back(wall[i]);
		mp1[wall[i]]=i;
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&ham[i]);
		node.push_back(ham[i]);
		mp2[ham[i]]=i;
	}
	sort(node.begin(),node.end());
	int st,ed,sz=node.size()-2;
	for(int i=0;i<=sz;i++)
	{
		if(node[i]==0) st=i;
		if(node[i]==x) ed=i;
	}
	for(int i=0;i<N;i++)
	{
		for(int j=0;j<N;j++)
		{
			f[i][j][0]=f[i][j][1]=9e18;
		}
	}
	mp2[x]=n+1;
	for(int i=1;i<=sz;i++)
	{
		for(int j=1;j<=n;j++)
		{
			pre[i][j]=pre[i-1][j];//前i个点哪些锤子出现了
			if(mp2[node[i]])//是锤子
			{
				pre[i][mp2[node[i]]]++;
				isham[i]=1;
			}
			else
			{
				pt[i]=mp1[node[i]];//墙的种类
			}
		}
	}
	f[st][st][0]=f[st][st][1]=0;
	
	for(int i=1;i<=sz;i++)//枚举长度
	{
		for(int j=1;j+i-1<=sz;j++)//枚举起点
		{
			int l=j,r=i+j-1;
			f[l][r][0]=min(f[l][r][0],f[l][r][1]+node[r]-node[l]);
			f[l][r][1]=min(f[l][r][1],f[l][r][0]+node[r]-node[l]);
			//i->往左走走到i-1
			//是锤子或者是墙然后有对应的锤子
			if(isham[l-1]||(pre[r][pt[l-1]]-pre[l-1][pt[l-1]]>0))
			{
				f[l-1][r][0]=min(f[l-1][r][0],f[l][r][0]+node[l]-node[l-1]);
			}
			//i->往右走走到i+1
			if(isham[r+1]||(pre[r][pt[r+1]]-pre[l-1][pt[r+1]]>0))
			{
				f[l][r+1][1]=min(f[l][r+1][1],f[l][r][1]+node[r+1]-node[r]);
			}
			if(l==ed) res=min(res,f[l][r][0]);
			if(r==ed) res=min(res,f[l][r][1]);
		}
	}
	if(res==9e18)puts("-1");
	else cout<<res<<endl;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值