题目描述
在坐标系中,有
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[i−1][j][1]
第
i
−
1
i-1
i−1 个点有三种情况
1.
是锤子
那么可以直接拿起来走
2.
是终点
那么可以走(这么看的话有点麻烦,所以在后面为了方便,设置
x
x
x 上有一个锤子)
即 mp2[x] = n+1;
3.
是墙
如果想走到这里的话,我们必须有第
i
−
1
i-1
i−1 号锤子
在这里我们用前缀和的思想来判断它有没有锤子
用
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 i−1 号锤子就可以走到 i − 1 i-1 i−1 号墙
综上所述 由 i i i -> i − 1 i-1 i−1 更新方法如下
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;
}